Merge "FBE notification is updated when language changed"
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index b71d596..5998ab7 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -271,34 +271,110 @@
/**
* Tests starting an uninitialized user, with wait times in between iterations.
- * Measures the time until the ProgressListener callback.
+ *
+ * The first iteration will take longer due to the process of setting policy permissions for
+ * a new user.
*/
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
- public void startUser_realistic() throws RemoteException {
+ public void startUser_uninitializedUser() throws RemoteException {
+ startUser_measuresAfterFirstIterations(/* numberOfIterationsToSkip */0);
+ }
+
+ /**
+ * Tests the second iteration of start user that has a problem that it takes too long to run, a
+ * bug has been created (b/266574680) and after investigating or fix this problem,
+ * this test can be removed.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void startUser_startOnceBefore() throws RemoteException {
+ startUser_measuresAfterFirstIterations(/* numberOfIterationsToSkip */1);
+ }
+
+ /**
+ * Tests a specific iteration of the start user process.
+ * Measures the time until ACTION_USER_STARTED is received.
+ * @param numberOfIterationsToSkip number of iterations that must be skipped in the preStartUser
+ * method.
+ */
+ private void startUser_measuresAfterFirstIterations(int numberOfIterationsToSkip)
+ throws RemoteException {
+ /**
+ * Run start user and stop for the next iteration, measures time while mRunner.keepRunning()
+ * return true.
+ */
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
+
final int userId = createUserNoFlags();
- final ProgressWaiter waiter = new ProgressWaiter();
+
+ preStartUser(userId, numberOfIterationsToSkip);
waitForBroadcastIdle();
- mRunner.resumeTiming();
- Log.i(TAG, "Starting timer");
+ waitCoolDownPeriod();
- final boolean success = mIam.startUserInBackgroundWithListener(userId, waiter)
- && waiter.waitForFinish(TIMEOUT_IN_SECOND * 1000);
+ runThenWaitForBroadcasts(userId, () -> {
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ mIam.startUserInBackground(userId);
+ }, Intent.ACTION_USER_STARTED);
mRunner.pauseTiming();
Log.i(TAG, "Stopping timer");
- assertTrue("Error: could not start user " + userId, success);
-
removeUser(userId);
- waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
/**
+ * Tests starting an initialized user, with wait times in between iterations stopping between
+ * iterations,this test will skip the first two iterations and only measure the next ones.
+ *
+ * The first iteration will take longer due to the process of setting policy permissions for
+ * a new user.
+ *
+ * The second iteration takes longer than expected and has a bug (b/266574680) to investigate
+ * it.
+ *
+ * The next iterations take the expected time to start a user.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void startUser_startTwiceBefore() throws RemoteException {
+ final int userId = createUserNoFlags();
+
+ //TODO(b/266681181) Reduce iteration number by 1 after investigation and possible fix.
+ preStartUser(userId, /* numberOfIterations */2);
+
+ /**
+ * Run start user and stop for the next iteration, measures time while mRunner.keepRunning()
+ * return true.
+ */
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+
+ waitForBroadcastIdle();
+ waitCoolDownPeriod();
+
+ runThenWaitForBroadcasts(userId, () -> {
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ mIam.startUserInBackground(userId);
+ }, Intent.ACTION_USER_STARTED);
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+
+ stopUser(userId, /* force */true);
+ mRunner.resumeTimingForNextIteration();
+ }
+
+ removeUser(userId);
+ }
+
+
+ /**
* Tests starting & unlocking an uninitialized user.
* Measures the time until unlock listener is triggered and user is unlocked.
*/
@@ -321,6 +397,46 @@
}
/**
+ * Tests starting & unlocking an initialized user, stopping the user at the end simulating real
+ * usage where the user is not removed after created and initialized.
+ * Measures the time until unlock listener is triggered and user is unlocked.
+ * This test will skip the first two iterations and only measure the next ones.
+ *
+ * The first iteration will take longer due to the process of setting policy permissions for a
+ * new user.
+ *
+ * The second iteration takes longer than expected and has a bug (b/266574680) to investigate
+ * it.
+ *
+ * The next iterations take the expected time to start a user.
+ */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void startAndUnlockUser_startTwiceBefore() throws RemoteException {
+ final int userId = createUserNoFlags();
+
+ //TODO(b/266681181) Reduce iteration number by 1 after investigation and possible fix.
+ preStartUser(userId, /* numberOfIterations */2);
+
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+
+ waitCoolDownPeriod();
+ mRunner.resumeTiming();
+ Log.i(TAG, "Starting timer");
+
+ // Waits for UserState.mUnlockProgress.finish().
+ startUserInBackgroundAndWaitForUnlock(userId);
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ stopUser(userId, /* force */true);
+ mRunner.resumeTimingForNextIteration();
+ }
+
+ removeUser(userId);
+ }
+
+ /**
* Tests starting & unlocking an uninitialized user.
* Measures the time until unlock listener is triggered and user is unlocked.
*/
@@ -404,28 +520,36 @@
}
}
- /** Tests switching to a previously-started, but no-longer-running, user with wait
- * times between iterations */
+ /**
+ * Tests switching to a previously-started, but no-longer-running, user with wait
+ * times between iterations
+ **/
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void switchUser_stopped_realistic() throws RemoteException {
- final int startUser = ActivityManager.getCurrentUser();
- final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ true);
+ final int currentUserId = ActivityManager.getCurrentUser();
+ final int userId = initializeNewUserAndSwitchBack(/* stopNewUser */ true);
+
+ /**
+ * Skip the second iteration of start user process that is taking a long time to finish.
+ */
+ preStartUser(userId, /* numberOfIterations */1);
+
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
waitCoolDownPeriod();
Log.d(TAG, "Starting timer");
mRunner.resumeTiming();
- switchUser(testUser);
+ switchUser(userId);
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
- switchUserNoCheck(startUser);
- stopUserAfterWaitingForBroadcastIdle(testUser, true);
- attestFalse("Failed to stop user " + testUser, mAm.isUserRunning(testUser));
+ switchUserNoCheck(currentUserId);
+ stopUserAfterWaitingForBroadcastIdle(userId, /* force */true);
+ attestFalse("Failed to stop user " + userId, mAm.isUserRunning(userId));
mRunner.resumeTimingForNextIteration();
}
- removeUser(testUser);
+ removeUser(userId);
}
/** Tests switching to a previously-started, but no-longer-running, user with wait
@@ -1378,6 +1502,24 @@
}
}
+ /**
+ * Start the user and stop after that, will repeat numberOfIterations times.
+ * Make sure the user is started before proceeding with the test.
+ * @param userId identifier of the user that will be started.
+ * @param numberOfIterations number of iterations that must be skipped.
+ */
+ private void preStartUser(int userId, int numberOfIterations) throws RemoteException {
+ for (int i = 0; i < numberOfIterations; i++) {
+ final ProgressWaiter preWaiter = new ProgressWaiter();
+
+ final boolean preStartComplete = mIam.startUserInBackgroundWithListener(userId,
+ preWaiter) && preWaiter.waitForFinish(TIMEOUT_IN_SECOND * 1000);
+ stopUserAfterWaitingForBroadcastIdle(userId, /* force */true);
+
+ assertTrue("Pre start was not performed for user" + userId, preStartComplete);
+ }
+ }
+
private void fail(@NonNull String message) {
Log.e(TAG, "Test failed on iteration #" + mRunner.getIteration() + ": " + message);
mRunner.markAsFailed(new AssertionError(message));
diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
index 5d67c96..50a7a19 100644
--- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
+++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
@@ -230,9 +230,10 @@
android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
@Override
- public void stopUserVisibleJobsForUser(@NonNull String packageName, int userId) {
+ public void notePendingUserRequestedAppStop(@NonNull String packageName, int userId,
+ @Nullable String debugReason) {
try {
- mBinder.stopUserVisibleJobsForUser(packageName, userId);
+ mBinder.notePendingUserRequestedAppStop(packageName, userId, debugReason);
} catch (RemoteException e) {
}
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
index a1f1954..1ea0c5b 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
@@ -48,5 +48,5 @@
@EnforcePermission(allOf={"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
void unregisterUserVisibleJobObserver(in IUserVisibleJobObserver observer);
@EnforcePermission(allOf={"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
- void stopUserVisibleJobsForUser(String packageName, int userId);
+ void notePendingUserRequestedAppStop(String packageName, int userId, String debugReason);
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index e0fffb4..d4110a3 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1897,7 +1897,11 @@
* <p>
* All user-initiated jobs must have an associated notification, set via
* {@link JobService#setNotification(JobParameters, int, Notification, int)}, and will be
- * shown in the Task Manager when running.
+ * shown in the Task Manager when running. These jobs cannot be rescheduled by the app
+ * if the user stops the job via system provided affordance (such as the Task Manager).
+ * Thus, it is best practice and recommended to provide action buttons in the
+ * associated notification to allow the user to stop the job gracefully
+ * and allow for rescheduling.
*
* <p>
* If the app doesn't hold the {@link android.Manifest.permission#RUN_LONG_JOBS} permission
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 4aec484..3ba5c31 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -515,5 +515,6 @@
android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
@SuppressWarnings("HiddenAbstractMethod")
- public abstract void stopUserVisibleJobsForUser(@NonNull String packageName, int userId);
+ public abstract void notePendingUserRequestedAppStop(@NonNull String packageName, int userId,
+ @Nullable String debugReason);
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index 1a205d0..2ab4324 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -156,6 +156,12 @@
* a future idle maintenance window.
* </p>
*
+ * <p class="note">
+ * Any {@link JobInfo.Builder#setUserInitiated(boolean) user-initiated job}
+ * cannot be rescheduled when the user has asked to stop the app
+ * via a system provided affordance (such as the Task Manager).
+ * In such situations, the value of {@code wantsReschedule} is always treated as {@code false}.
+ *
* @param params The parameters identifying this job, as supplied to
* the job in the {@link #onStartJob(JobParameters)} callback.
* @param wantsReschedule {@code true} if this job should be rescheduled according
@@ -220,6 +226,12 @@
* Once this method returns (or times out), the system releases the wakelock that it is holding
* on behalf of the job.</p>
*
+ * <p class="note">
+ * Any {@link JobInfo.Builder#setUserInitiated(boolean) user-initiated job}
+ * cannot be rescheduled when stopped by the user via a system provided affordance (such as
+ * the Task Manager). In such situations, the returned value from this method call is always
+ * treated as {@code false}.
+ *
* <p class="caution"><strong>Note:</strong> When a job is stopped and rescheduled via this
* method call, the deadline constraint is excluded from the rescheduled job's constraint set.
* The rescheduled job will run again once all remaining constraints are satisfied.
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 d7163d8..5599e54 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -1933,8 +1933,9 @@
Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
- mTimeTickOptions = BroadcastOptions
- .makeRemovingMatchingFilter(new IntentFilter(Intent.ACTION_TIME_TICK))
+ mTimeTickOptions = BroadcastOptions.makeBasic()
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ .setDeferUntilActive(true)
.toBundle();
mTimeTickTrigger = new IAlarmListener.Stub() {
@Override
@@ -4252,8 +4253,8 @@
}
}
// And send a TIME_TICK right now, since it is important to get the UI updated.
- mHandler.post(() ->
- getContext().sendBroadcastAsUser(mTimeTickIntent, UserHandle.ALL));
+ mHandler.post(() -> getContext().sendBroadcastAsUser(mTimeTickIntent,
+ UserHandle.ALL, null, mTimeTickOptions));
} else {
mNonInteractiveStartTime = nowELAPSED;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 62d97358..a474cc8 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1282,17 +1282,20 @@
}
@GuardedBy("mLock")
- void stopUserVisibleJobsLocked(int userId, @NonNull String packageName,
- @JobParameters.StopReason int reason, int internalReasonCode) {
+ void markJobsForUserStopLocked(int userId, @NonNull String packageName,
+ @Nullable String debugReason) {
for (int i = mActiveServices.size() - 1; i >= 0; --i) {
final JobServiceContext jsc = mActiveServices.get(i);
final JobStatus jobStatus = jsc.getRunningJobLocked();
- if (jobStatus != null && userId == jobStatus.getSourceUserId()
- && jobStatus.getSourcePackageName().equals(packageName)
- && jobStatus.isUserVisibleJob()) {
- jsc.cancelExecutingJobLocked(reason, internalReasonCode,
- JobParameters.getInternalReasonCodeDescription(internalReasonCode));
+ // Normally, we handle jobs primarily using the source package and userId,
+ // however, user-visible jobs are shown as coming from the calling app, so we
+ // need to operate on the jobs from that perspective here.
+ if (jobStatus != null && userId == jobStatus.getUserId()
+ && jobStatus.getServiceComponent().getPackageName().equals(packageName)) {
+ jsc.markForProcessDeathLocked(JobParameters.STOP_REASON_USER,
+ JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP,
+ debugReason);
}
}
}
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 e8fcdd2..fce75a2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1701,16 +1701,15 @@
}
@VisibleForTesting
- void stopUserVisibleJobsInternal(@NonNull String packageName, int userId) {
+ void notePendingUserRequestedAppStopInternal(@NonNull String packageName, int userId,
+ @Nullable String debugReason) {
final int packageUid = mLocalPM.getPackageUid(packageName, 0, userId);
if (packageUid < 0) {
Slog.wtf(TAG, "Asked to stop jobs of an unknown package");
return;
}
synchronized (mLock) {
- mConcurrencyManager.stopUserVisibleJobsLocked(userId, packageName,
- JobParameters.STOP_REASON_USER,
- JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP);
+ mConcurrencyManager.markJobsForUserStopLocked(userId, packageName, debugReason);
final ArraySet<JobStatus> jobs = mJobs.getJobsByUid(packageUid);
for (int i = jobs.size() - 1; i >= 0; i--) {
final JobStatus job = jobs.valueAt(i);
@@ -2387,12 +2386,25 @@
*
* @param failureToReschedule Provided job status that we will reschedule.
* @return A newly instantiated JobStatus with the same constraints as the last job except
- * with adjusted timing constraints.
+ * with adjusted timing constraints, or {@code null} if the job shouldn't be rescheduled for
+ * some policy reason.
* @see #maybeQueueReadyJobsForExecutionLocked
*/
+ @Nullable
@VisibleForTesting
JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule,
@JobParameters.StopReason int stopReason, int internalStopReason) {
+ if (internalStopReason == JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP
+ && failureToReschedule.isUserVisibleJob()) {
+ // If a user stops an app via Task Manager and the job was user-visible, then assume
+ // the user wanted to stop that task and not let it run in the future. It's in the
+ // app's best interests to provide action buttons in their notification to avoid this
+ // scenario.
+ Slog.i(TAG,
+ "Dropping " + failureToReschedule.toShortString() + " because of user stop");
+ return null;
+ }
+
final long elapsedNowMillis = sElapsedRealtimeClock.millis();
final JobInfo job = failureToReschedule.getJob();
@@ -4225,12 +4237,13 @@
@Override
@EnforcePermission(allOf = {MANAGE_ACTIVITY_TASKS, INTERACT_ACROSS_USERS_FULL})
- public void stopUserVisibleJobsForUser(@NonNull String packageName, int userId) {
- super.stopUserVisibleJobsForUser_enforcePermission();
+ public void notePendingUserRequestedAppStop(@NonNull String packageName, int userId,
+ @Nullable String debugReason) {
+ super.notePendingUserRequestedAppStop_enforcePermission();
if (packageName == null) {
throw new NullPointerException("packageName");
}
- JobSchedulerService.this.stopUserVisibleJobsInternal(packageName, userId);
+ notePendingUserRequestedAppStopInternal(packageName, userId, debugReason);
}
}
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 911744f..cc9e517 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -190,6 +190,14 @@
private Network mPendingNetworkChange;
+ /**
+ * The reason this job is marked for death. If it's not marked for death,
+ * then the value should be {@link JobParameters#STOP_REASON_UNDEFINED}.
+ */
+ private int mDeathMarkStopReason = JobParameters.STOP_REASON_UNDEFINED;
+ private int mDeathMarkInternalStopReason;
+ private String mDeathMarkDebugReason;
+
// Debugging: reason this job was last stopped.
public String mStoppedReason;
@@ -452,6 +460,7 @@
mStoppedReason = null;
mStoppedTime = 0;
job.startedAsExpeditedJob = job.shouldTreatAsExpeditedJob();
+ job.startedAsUserInitiatedJob = job.shouldTreatAsUserInitiatedJob();
return true;
}
}
@@ -502,6 +511,34 @@
doCancelLocked(reason, internalStopReason, debugReason);
}
+ /**
+ * Called when an app's process is about to be killed and we want to update the job's stop
+ * reasons without telling the job it's going to be stopped.
+ */
+ @GuardedBy("mLock")
+ void markForProcessDeathLocked(@JobParameters.StopReason int reason,
+ int internalStopReason, @NonNull String debugReason) {
+ if (mVerb == VERB_FINISHED) {
+ if (DEBUG) {
+ Slog.d(TAG, "Too late to mark for death (verb=" + mVerb + "), ignoring.");
+ }
+ return;
+ }
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Marking " + mRunningJob.toShortString() + " for death because "
+ + reason + ":" + debugReason);
+ }
+ mDeathMarkStopReason = reason;
+ mDeathMarkInternalStopReason = internalStopReason;
+ mDeathMarkDebugReason = debugReason;
+ if (mParams.getStopReason() == JobParameters.STOP_REASON_UNDEFINED) {
+ // Only set the stop reason if we're not already trying to stop the job for some
+ // other reason in case that other stop is successful before the process dies.
+ mParams.setStopReason(reason, internalStopReason, debugReason);
+ }
+ }
+
int getPreferredUid() {
return mPreferredUid;
}
@@ -648,7 +685,7 @@
"last work dequeued");
// This will finish the job.
doCallbackLocked(false, "last work dequeued");
- } else {
+ } else if (work != null) {
// Delivery count has been updated, so persist JobWorkItem change.
mService.mJobs.touchJob(mRunningJob);
}
@@ -754,6 +791,12 @@
@Override
public void onServiceDisconnected(ComponentName name) {
synchronized (mLock) {
+ if (mDeathMarkStopReason != JobParameters.STOP_REASON_UNDEFINED) {
+ // Service "unexpectedly" disconnected, but we knew the process was going to die.
+ // Use that as the stop reason for logging/debugging purposes.
+ mParams.setStopReason(
+ mDeathMarkStopReason, mDeathMarkInternalStopReason, mDeathMarkDebugReason);
+ }
closeAndCleanupJobLocked(true /* needsReschedule */, "unexpectedly disconnected");
}
}
@@ -1182,29 +1225,51 @@
* we want to clean up internally.
*/
@GuardedBy("mLock")
- private void closeAndCleanupJobLocked(boolean reschedule, @Nullable String reason) {
+ private void closeAndCleanupJobLocked(boolean reschedule, @Nullable String loggingDebugReason) {
final JobStatus completedJob;
if (mVerb == VERB_FINISHED) {
return;
}
if (DEBUG) {
Slog.d(TAG, "Cleaning up " + mRunningJob.toShortString()
- + " reschedule=" + reschedule + " reason=" + reason);
+ + " reschedule=" + reschedule + " reason=" + loggingDebugReason);
}
- applyStoppedReasonLocked(reason);
+ applyStoppedReasonLocked(loggingDebugReason);
completedJob = mRunningJob;
- final int internalStopReason = mParams.getInternalStopReasonCode();
- final int stopReason = mParams.getStopReason();
+ // Use the JobParameters stop reasons for logging and metric purposes,
+ // but if the job was marked for death, use that reason for rescheduling purposes.
+ // The discrepancy could happen if a job ends up stopping for some reason
+ // in the time between the job being marked and the process actually dying.
+ // Since the job stopped for another reason, we want to log the actual stop reason
+ // for the sake of accurate metrics and debugging,
+ // but we should use the death mark reasons when determining reschedule policy.
+ final int loggingStopReason = mParams.getStopReason();
+ final int loggingInternalStopReason = mParams.getInternalStopReasonCode();
+ final int reschedulingStopReason, reschedulingInternalStopReason;
+ if (mDeathMarkStopReason != JobParameters.STOP_REASON_UNDEFINED) {
+ if (DEBUG) {
+ Slog.d(TAG, "Job marked for death because of "
+ + JobParameters.getInternalReasonCodeDescription(
+ mDeathMarkInternalStopReason)
+ + ": " + mDeathMarkDebugReason);
+ }
+ reschedulingStopReason = mDeathMarkStopReason;
+ reschedulingInternalStopReason = mDeathMarkInternalStopReason;
+ } else {
+ reschedulingStopReason = loggingStopReason;
+ reschedulingInternalStopReason = loggingInternalStopReason;
+ }
mPreviousJobHadSuccessfulFinish =
- (internalStopReason == JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
+ (loggingInternalStopReason == JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
if (!mPreviousJobHadSuccessfulFinish) {
mLastUnsuccessfulFinishElapsed = sElapsedRealtimeClock.millis();
}
- mJobPackageTracker.noteInactive(completedJob, internalStopReason, reason);
+ mJobPackageTracker.noteInactive(completedJob,
+ loggingInternalStopReason, loggingDebugReason);
FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
completedJob.getSourceUid(), null, completedJob.getBatteryName(),
FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__FINISHED,
- internalStopReason, completedJob.getStandbyBucket(), completedJob.getJobId(),
+ loggingInternalStopReason, completedJob.getStandbyBucket(), completedJob.getJobId(),
completedJob.hasChargingConstraint(),
completedJob.hasBatteryNotLowConstraint(),
completedJob.hasStorageNotLowConstraint(),
@@ -1215,7 +1280,7 @@
completedJob.hasContentTriggerConstraint(),
completedJob.isRequestedExpeditedJob(),
completedJob.startedAsExpeditedJob,
- stopReason,
+ loggingStopReason,
completedJob.getJob().isPrefetch(),
completedJob.getJob().getPriority(),
completedJob.getEffectivePriority(),
@@ -1235,11 +1300,11 @@
}
try {
mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(),
- internalStopReason);
+ loggingInternalStopReason);
} catch (RemoteException e) {
// Whatever.
}
- if (mParams.getStopReason() == JobParameters.STOP_REASON_TIMEOUT) {
+ if (loggingStopReason == JobParameters.STOP_REASON_TIMEOUT) {
mEconomyManagerInternal.noteInstantaneousEvent(
mRunningJob.getSourceUserId(), mRunningJob.getSourcePackageName(),
JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT,
@@ -1260,6 +1325,9 @@
mCancelled = false;
service = null;
mAvailable = true;
+ mDeathMarkStopReason = JobParameters.STOP_REASON_UNDEFINED;
+ mDeathMarkInternalStopReason = 0;
+ mDeathMarkDebugReason = null;
mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED;
mPendingInternalStopReason = 0;
mPendingDebugStopReason = null;
@@ -1268,8 +1336,8 @@
if (completedJob.isUserVisibleJob()) {
mService.informObserversOfUserVisibleJobChange(this, completedJob, false);
}
- mCompletedListener.onJobCompletedLocked(completedJob, stopReason, internalStopReason,
- reschedule);
+ mCompletedListener.onJobCompletedLocked(completedJob,
+ reschedulingStopReason, reschedulingInternalStopReason, reschedule);
mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
}
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 ce33a8e..1971a11 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
@@ -402,6 +402,11 @@
* running. This isn't copied over when a job is rescheduled.
*/
public boolean startedAsExpeditedJob = false;
+ /**
+ * Whether or not this particular JobStatus instance was treated as a user-initiated job
+ * when it started running. This isn't copied over when a job is rescheduled.
+ */
+ public boolean startedAsUserInitiatedJob = false;
public boolean startedWithImmediacyPrivilege = false;
@@ -1407,7 +1412,7 @@
* @return true if this is a job whose execution should be made visible to the user.
*/
public boolean isUserVisibleJob() {
- return shouldTreatAsUserInitiatedJob();
+ return shouldTreatAsUserInitiatedJob() || startedAsUserInitiatedJob;
}
/**
@@ -2568,6 +2573,13 @@
pw.print(startedAsExpeditedJob);
pw.println(")");
}
+ if ((getFlags() & JobInfo.FLAG_USER_INITIATED) != 0) {
+ pw.print("userInitiatedApproved: ");
+ pw.print(shouldTreatAsUserInitiatedJob());
+ pw.print(" (started as UIJ: ");
+ pw.print(startedAsUserInitiatedJob);
+ pw.println(")");
+ }
pw.decreaseIndent();
if (changedAuthorities != null) {
diff --git a/core/api/current.txt b/core/api/current.txt
index bca9913..f2ca78f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -121,6 +121,7 @@
field public static final String INTERACT_ACROSS_PROFILES = "android.permission.INTERACT_ACROSS_PROFILES";
field public static final String INTERNET = "android.permission.INTERNET";
field public static final String KILL_BACKGROUND_PROCESSES = "android.permission.KILL_BACKGROUND_PROCESSES";
+ field public static final String LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE = "android.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE";
field public static final String LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK = "android.permission.LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK";
field public static final String LOADER_USAGE_STATS = "android.permission.LOADER_USAGE_STATS";
field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
@@ -3538,6 +3539,7 @@
method public float getCenterY();
method public int getMode();
method public float getScale();
+ method public boolean isActivated();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.MagnificationConfig> CREATOR;
field public static final int MAGNIFICATION_MODE_DEFAULT = 0; // 0x0
@@ -3548,6 +3550,7 @@
public static final class MagnificationConfig.Builder {
ctor public MagnificationConfig.Builder();
method @NonNull public android.accessibilityservice.MagnificationConfig build();
+ method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setActivated(boolean);
method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setCenterX(float);
method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setCenterY(float);
method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setMode(int);
@@ -5204,6 +5207,13 @@
field @NonNull public static final android.os.Parcelable.Creator<android.app.BackgroundServiceStartNotAllowedException> CREATOR;
}
+ public class BroadcastOptions {
+ method public boolean isShareIdentityEnabled();
+ method @NonNull public static android.app.BroadcastOptions makeBasic();
+ method @NonNull public android.app.BroadcastOptions setShareIdentityEnabled(boolean);
+ method @NonNull public android.os.Bundle toBundle();
+ }
+
public class DatePickerDialog extends android.app.AlertDialog implements android.widget.DatePicker.OnDateChangedListener android.content.DialogInterface.OnClickListener {
ctor public DatePickerDialog(@NonNull android.content.Context);
ctor public DatePickerDialog(@NonNull android.content.Context, @StyleRes int);
@@ -7188,6 +7198,7 @@
}
public class StatusBarManager {
+ method @RequiresPermission(android.Manifest.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE) public boolean canLaunchCaptureContentActivityForNote(@NonNull android.app.Activity);
method public void requestAddTileService(@NonNull android.content.ComponentName, @NonNull CharSequence, @NonNull android.graphics.drawable.Icon, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
field public static final int TILE_ADD_REQUEST_ERROR_APP_NOT_IN_FOREGROUND = 1004; // 0x3ec
field public static final int TILE_ADD_REQUEST_ERROR_BAD_COMPONENT = 1002; // 0x3ea
@@ -9567,6 +9578,8 @@
method public final int getResultCode();
method public final String getResultData();
method public final android.os.Bundle getResultExtras(boolean);
+ method @Nullable public String getSentFromPackage();
+ method public int getSentFromUid();
method public final android.content.BroadcastReceiver.PendingResult goAsync();
method public final boolean isInitialStickyBroadcast();
method public final boolean isOrderedBroadcast();
@@ -10167,11 +10180,14 @@
method public abstract void revokeUriPermission(String, android.net.Uri, int);
method public abstract void sendBroadcast(@RequiresPermission android.content.Intent);
method public abstract void sendBroadcast(@RequiresPermission android.content.Intent, @Nullable String);
+ method public void sendBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle);
method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle);
method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String);
method public void sendBroadcastWithMultiplePermissions(@NonNull android.content.Intent, @NonNull String[]);
method public abstract void sendOrderedBroadcast(@RequiresPermission android.content.Intent, @Nullable String);
+ method public void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle);
method public abstract void sendOrderedBroadcast(@NonNull @RequiresPermission android.content.Intent, @Nullable String, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
+ method public void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
method public void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable String, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public abstract void sendOrderedBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
method @Deprecated @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY) public abstract void sendStickyBroadcast(@RequiresPermission android.content.Intent);
@@ -10697,6 +10713,7 @@
field public static final String ACTION_INSERT_OR_EDIT = "android.intent.action.INSERT_OR_EDIT";
field public static final String ACTION_INSTALL_FAILURE = "android.intent.action.INSTALL_FAILURE";
field @Deprecated public static final String ACTION_INSTALL_PACKAGE = "android.intent.action.INSTALL_PACKAGE";
+ field @RequiresPermission(android.Manifest.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE) public static final String ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE = "android.intent.action.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE";
field public static final String ACTION_LOCALE_CHANGED = "android.intent.action.LOCALE_CHANGED";
field public static final String ACTION_LOCKED_BOOT_COMPLETED = "android.intent.action.LOCKED_BOOT_COMPLETED";
field public static final String ACTION_MAIN = "android.intent.action.MAIN";
@@ -10790,6 +10807,11 @@
field public static final String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
field @Deprecated public static final String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
field public static final String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH";
+ field public static final int CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN = 4; // 0x4
+ field public static final int CAPTURE_CONTENT_FOR_NOTE_FAILED = 1; // 0x1
+ field public static final int CAPTURE_CONTENT_FOR_NOTE_SUCCESS = 0; // 0x0
+ field public static final int CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED = 2; // 0x2
+ field public static final int CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED = 3; // 0x3
field public static final String CATEGORY_ACCESSIBILITY_SHORTCUT_TARGET = "android.intent.category.ACCESSIBILITY_SHORTCUT_TARGET";
field public static final String CATEGORY_ALTERNATIVE = "android.intent.category.ALTERNATIVE";
field public static final String CATEGORY_APP_BROWSER = "android.intent.category.APP_BROWSER";
@@ -10845,12 +10867,14 @@
field public static final String EXTRA_AUTO_LAUNCH_SINGLE_CHOICE = "android.intent.extra.AUTO_LAUNCH_SINGLE_CHOICE";
field public static final String EXTRA_BCC = "android.intent.extra.BCC";
field public static final String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT";
+ field public static final String EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE = "android.intent.extra.CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE";
field public static final String EXTRA_CC = "android.intent.extra.CC";
field @Deprecated public static final String EXTRA_CHANGED_COMPONENT_NAME = "android.intent.extra.changed_component_name";
field public static final String EXTRA_CHANGED_COMPONENT_NAME_LIST = "android.intent.extra.changed_component_name_list";
field public static final String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list";
field public static final String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list";
field public static final String EXTRA_CHOOSER_CUSTOM_ACTIONS = "android.intent.extra.CHOOSER_CUSTOM_ACTIONS";
+ field public static final String EXTRA_CHOOSER_PAYLOAD_RESELECTION_ACTION = "android.intent.extra.CHOOSER_PAYLOAD_RESELECTION_ACTION";
field public static final String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER";
field public static final String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS";
field public static final String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
@@ -12093,11 +12117,11 @@
public static final class PackageInstaller.InstallConstraints implements android.os.Parcelable {
method public int describeContents();
- method public boolean isRequireAppNotForeground();
- method public boolean isRequireAppNotInteracting();
- method public boolean isRequireAppNotTopVisible();
- method public boolean isRequireDeviceIdle();
- method public boolean isRequireNotInCall();
+ method public boolean isAppNotForegroundRequired();
+ method public boolean isAppNotInteractingRequired();
+ method public boolean isAppNotTopVisibleRequired();
+ method public boolean isDeviceIdleRequired();
+ method public boolean isNotInCallRequired();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.InstallConstraints> CREATOR;
field @NonNull public static final android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE;
@@ -12106,16 +12130,16 @@
public static final class PackageInstaller.InstallConstraints.Builder {
ctor public PackageInstaller.InstallConstraints.Builder();
method @NonNull public android.content.pm.PackageInstaller.InstallConstraints build();
- method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground();
- method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting();
- method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible();
- method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle();
- method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotForegroundRequired();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotInteractingRequired();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotTopVisibleRequired();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder setDeviceIdleRequired();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder setNotInCallRequired();
}
public static final class PackageInstaller.InstallConstraintsResult implements android.os.Parcelable {
+ method public boolean areAllConstraintsSatisfied();
method public int describeContents();
- method public boolean isAllConstraintsSatisfied();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.InstallConstraintsResult> CREATOR;
}
@@ -12182,12 +12206,12 @@
method @Nullable public String getAppPackageName();
method @NonNull public int[] getChildSessionIds();
method public long getCreatedMillis();
+ method public boolean getDontKillApp();
method public int getInstallLocation();
method public int getInstallReason();
method @Nullable public String getInstallerAttributionTag();
method @Nullable public String getInstallerPackageName();
method public int getInstallerUid();
- method @NonNull public boolean getIsPreApprovalRequested();
method public int getMode();
method public int getOriginatingUid();
method @Nullable public android.net.Uri getOriginatingUri();
@@ -12207,6 +12231,7 @@
method public boolean isApplicationEnabledSettingPersistent();
method public boolean isCommitted();
method public boolean isMultiPackage();
+ method public boolean isPreApprovalRequested();
method public boolean isRequestUpdateOwnership();
method public boolean isSealed();
method public boolean isStaged();
@@ -12237,6 +12262,7 @@
method public void setAppPackageName(@Nullable String);
method public void setApplicationEnabledSettingPersistent();
method @Deprecated public void setAutoRevokePermissionsMode(boolean);
+ method public void setDontKillApp(boolean);
method public void setInstallLocation(int);
method public void setInstallReason(int);
method public void setInstallScenario(int);
@@ -13483,6 +13509,18 @@
method public void unregisterCredentialDescription(@NonNull android.credentials.UnregisterCredentialDescriptionRequest);
}
+ public final class CredentialOption implements android.os.Parcelable {
+ ctor public CredentialOption(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
+ method public int describeContents();
+ method @NonNull public android.os.Bundle getCandidateQueryData();
+ method @NonNull public android.os.Bundle getCredentialRetrievalData();
+ method @NonNull public String getType();
+ method public boolean isSystemProviderRequired();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CredentialOption> CREATOR;
+ field public static final String FLATTENED_REQUEST = "android.credentials.GetCredentialOption.FLATTENED_REQUEST_STRING";
+ }
+
public class GetCredentialException extends java.lang.Exception {
ctor public GetCredentialException(@NonNull String, @Nullable String);
ctor public GetCredentialException(@NonNull String, @Nullable String, @Nullable Throwable);
@@ -13495,31 +13533,19 @@
field @NonNull public static final String TYPE_USER_CANCELED = "android.credentials.GetCredentialException.TYPE_USER_CANCELED";
}
- public final class GetCredentialOption implements android.os.Parcelable {
- ctor public GetCredentialOption(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
- method public int describeContents();
- method @NonNull public android.os.Bundle getCandidateQueryData();
- method @NonNull public android.os.Bundle getCredentialRetrievalData();
- method @NonNull public String getType();
- method public boolean isSystemProviderRequired();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.credentials.GetCredentialOption> CREATOR;
- field public static final String FLATTENED_REQUEST = "android.credentials.GetCredentialOption.FLATTENED_REQUEST_STRING";
- }
-
public final class GetCredentialRequest implements android.os.Parcelable {
method public int describeContents();
+ method @NonNull public java.util.List<android.credentials.CredentialOption> getCredentialOptions();
method @NonNull public android.os.Bundle getData();
- method @NonNull public java.util.List<android.credentials.GetCredentialOption> getGetCredentialOptions();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.credentials.GetCredentialRequest> CREATOR;
}
public static final class GetCredentialRequest.Builder {
ctor public GetCredentialRequest.Builder(@NonNull android.os.Bundle);
- method @NonNull public android.credentials.GetCredentialRequest.Builder addGetCredentialOption(@NonNull android.credentials.GetCredentialOption);
+ method @NonNull public android.credentials.GetCredentialRequest.Builder addCredentialOption(@NonNull android.credentials.CredentialOption);
method @NonNull public android.credentials.GetCredentialRequest build();
- method @NonNull public android.credentials.GetCredentialRequest.Builder setGetCredentialOptions(@NonNull java.util.List<android.credentials.GetCredentialOption>);
+ method @NonNull public android.credentials.GetCredentialRequest.Builder setCredentialOptions(@NonNull java.util.List<android.credentials.CredentialOption>);
}
public final class GetCredentialResponse implements android.os.Parcelable {
@@ -19341,7 +19367,9 @@
public final class DisplayManager {
method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int);
+ method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, float, @Nullable android.view.Surface, int);
method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable android.hardware.display.VirtualDisplay.Callback, @Nullable android.os.Handler);
+ method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, float, @Nullable android.view.Surface, int, @Nullable android.os.Handler, @Nullable android.hardware.display.VirtualDisplay.Callback);
method public android.view.Display getDisplay(int);
method public android.view.Display[] getDisplays();
method public android.view.Display[] getDisplays(String);
@@ -24578,8 +24606,8 @@
public final class RouteListingPreference implements android.os.Parcelable {
method public int describeContents();
- method @Nullable public android.content.ComponentName getInAppOnlyItemRoutingReceiver();
method @NonNull public java.util.List<android.media.RouteListingPreference.Item> getItems();
+ method @Nullable public android.content.ComponentName getLinkedItemComponentName();
method public boolean getUseSystemOrdering();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final String ACTION_TRANSFER_MEDIA = "android.media.action.TRANSFER_MEDIA";
@@ -24590,37 +24618,39 @@
public static final class RouteListingPreference.Builder {
ctor public RouteListingPreference.Builder();
method @NonNull public android.media.RouteListingPreference build();
- method @NonNull public android.media.RouteListingPreference.Builder setInAppOnlyItemRoutingReceiver(@Nullable android.content.ComponentName);
method @NonNull public android.media.RouteListingPreference.Builder setItems(@NonNull java.util.List<android.media.RouteListingPreference.Item>);
+ method @NonNull public android.media.RouteListingPreference.Builder setLinkedItemComponentName(@Nullable android.content.ComponentName);
method @NonNull public android.media.RouteListingPreference.Builder setUseSystemOrdering(boolean);
}
public static final class RouteListingPreference.Item implements android.os.Parcelable {
method public int describeContents();
- method @Nullable public CharSequence getCustomDisableReasonMessage();
- method public int getDisableReason();
+ method @Nullable public CharSequence getCustomSubtextMessage();
method public int getFlags();
method @NonNull public String getRouteId();
- method public int getSessionParticipantCount();
+ method public int getSelectionBehavior();
+ method public int getSubText();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteListingPreference.Item> CREATOR;
- field public static final int DISABLE_REASON_AD = 3; // 0x3
- field public static final int DISABLE_REASON_CUSTOM = 5; // 0x5
- field public static final int DISABLE_REASON_DOWNLOADED_CONTENT = 2; // 0x2
- field public static final int DISABLE_REASON_IN_APP_ONLY = 4; // 0x4
- field public static final int DISABLE_REASON_NONE = 0; // 0x0
- field public static final int DISABLE_REASON_SUBSCRIPTION_REQUIRED = 1; // 0x1
field public static final int FLAG_ONGOING_SESSION = 1; // 0x1
field public static final int FLAG_SUGGESTED_ROUTE = 2; // 0x2
+ field public static final int SELECTION_BEHAVIOR_GO_TO_APP = 2; // 0x2
+ field public static final int SELECTION_BEHAVIOR_NONE = 0; // 0x0
+ field public static final int SELECTION_BEHAVIOR_TRANSFER = 1; // 0x1
+ field public static final int SUBTEXT_AD_ROUTING_DISALLOWED = 3; // 0x3
+ field public static final int SUBTEXT_CUSTOM = 10000; // 0x2710
+ field public static final int SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED = 2; // 0x2
+ field public static final int SUBTEXT_NONE = 0; // 0x0
+ field public static final int SUBTEXT_SUBSCRIPTION_REQUIRED = 1; // 0x1
}
public static final class RouteListingPreference.Item.Builder {
ctor public RouteListingPreference.Item.Builder(@NonNull String);
method @NonNull public android.media.RouteListingPreference.Item build();
- method @NonNull public android.media.RouteListingPreference.Item.Builder setCustomDisableReasonMessage(@Nullable CharSequence);
- method @NonNull public android.media.RouteListingPreference.Item.Builder setDisableReason(int);
+ method @NonNull public android.media.RouteListingPreference.Item.Builder setCustomSubtextMessage(@Nullable CharSequence);
method @NonNull public android.media.RouteListingPreference.Item.Builder setFlags(int);
- method @NonNull public android.media.RouteListingPreference.Item.Builder setSessionParticipantCount(@IntRange(from=0) int);
+ method @NonNull public android.media.RouteListingPreference.Item.Builder setSelectionBehavior(int);
+ method @NonNull public android.media.RouteListingPreference.Item.Builder setSubText(int);
}
public final class RoutingSessionInfo implements android.os.Parcelable {
@@ -27414,6 +27444,8 @@
method public boolean onTrackballEvent(@NonNull android.view.MotionEvent);
method public void onTracksChanged(@NonNull java.util.List<android.media.tv.TvTrackInfo>);
method public void onTuned(@NonNull android.net.Uri);
+ method public void onTvRecordingInfo(@Nullable android.media.tv.TvRecordingInfo);
+ method public void onTvRecordingInfoList(@NonNull java.util.List<android.media.tv.TvRecordingInfo>);
method public void onVideoAvailable();
method public void onVideoUnavailable(int);
method @CallSuper public void removeBroadcastInfo(int);
@@ -27476,6 +27508,8 @@
method public void sendSigningResult(@NonNull String, @NonNull byte[]);
method public void sendStreamVolume(float);
method public void sendTrackInfoList(@Nullable java.util.List<android.media.tv.TvTrackInfo>);
+ method public void sendTvRecordingInfo(@Nullable android.media.tv.TvRecordingInfo);
+ method public void sendTvRecordingInfoList(@NonNull java.util.List<android.media.tv.TvRecordingInfo>);
method public void setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.interactive.TvInteractiveAppView.TvInteractiveAppCallback);
method public void setOnUnhandledInputEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.interactive.TvInteractiveAppView.OnUnhandledInputEventListener);
method public void setTeletextAppEnabled(boolean);
@@ -32115,9 +32149,11 @@
method public void onEarlyReportFinished();
method public void onError(int);
method public void onFinished();
+ method public void onFinished(@NonNull String);
method public void onProgress(@FloatRange(from=0.0f, to=100.0f) float);
field public static final int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS = 5; // 0x5
field public static final int BUGREPORT_ERROR_INVALID_INPUT = 1; // 0x1
+ field public static final int BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE = 6; // 0x6
field public static final int BUGREPORT_ERROR_RUNTIME = 2; // 0x2
field public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = 4; // 0x4
field public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT = 3; // 0x3
@@ -40259,10 +40295,10 @@
}
public final class GetCredentialRequest implements android.os.Parcelable {
- ctor public GetCredentialRequest(@NonNull android.service.credentials.CallingAppInfo, @NonNull android.credentials.GetCredentialOption);
+ ctor public GetCredentialRequest(@NonNull android.service.credentials.CallingAppInfo, @NonNull android.credentials.CredentialOption);
method public int describeContents();
method @NonNull public android.service.credentials.CallingAppInfo getCallingAppInfo();
- method @NonNull public android.credentials.GetCredentialOption getGetCredentialOption();
+ method @NonNull public android.credentials.CredentialOption getGetCredentialOption();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.GetCredentialRequest> CREATOR;
}
@@ -41624,7 +41660,6 @@
public final class CallControl {
method public void disconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method @NonNull public android.os.ParcelUuid getCallId();
- method public void rejectCall(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method public void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method public void setActive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method public void setInactive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
@@ -41673,7 +41708,6 @@
}
public final class CallException extends java.lang.RuntimeException implements android.os.Parcelable {
- ctor public CallException(@Nullable String);
ctor public CallException(@Nullable String, int);
method public int describeContents();
method public int getCode();
@@ -51356,8 +51390,12 @@
method public android.view.SurfaceControl getSurfaceControl();
method public void setChildSurfacePackage(@NonNull android.view.SurfaceControlViewHost.SurfacePackage);
method public void setSecure(boolean);
+ method public void setSurfaceLifecycle(int);
method public void setZOrderMediaOverlay(boolean);
method public void setZOrderOnTop(boolean);
+ field public static final int SURFACE_LIFECYCLE_DEFAULT = 0; // 0x0
+ field public static final int SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT = 2; // 0x2
+ field public static final int SURFACE_LIFECYCLE_FOLLOWS_VISIBILITY = 1; // 0x1
}
public class TextureView extends android.view.View {
@@ -52444,7 +52482,9 @@
method public int getScaledHoverSlop();
method public int getScaledMaximumDrawingCacheSize();
method public int getScaledMaximumFlingVelocity();
+ method public int getScaledMaximumFlingVelocity(int, int, int);
method public int getScaledMinimumFlingVelocity();
+ method public int getScaledMinimumFlingVelocity(int, int, int);
method public int getScaledMinimumScalingSpan();
method public int getScaledOverflingDistance();
method public int getScaledOverscrollDistance();
@@ -60292,7 +60332,7 @@
method @UiThread public void remove();
}
- public class SurfaceSyncGroup {
+ public final class SurfaceSyncGroup {
ctor public SurfaceSyncGroup(@NonNull String);
method @UiThread public boolean add(@Nullable android.view.AttachedSurfaceControl, @Nullable Runnable);
method public boolean add(@NonNull android.view.SurfaceControlViewHost.SurfacePackage, @Nullable Runnable);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index f5653516..0476d79 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -817,7 +817,6 @@
method public int getPendingIntentBackgroundActivityStartMode();
method public boolean isDeferUntilActive();
method @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed();
- method public static android.app.BroadcastOptions makeBasic();
method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS) public void recordResponseEventWhileInBackground(@IntRange(from=0) long);
method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
method @NonNull public android.app.BroadcastOptions setDeferUntilActive(boolean);
@@ -832,7 +831,6 @@
method public void setRequireNoneOfPermissions(@Nullable String[]);
method @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppAllowlist(long, int, int, @Nullable String);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(long);
- method public android.os.Bundle toBundle();
field public static final int DELIVERY_GROUP_POLICY_ALL = 0; // 0x0
field public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1; // 0x1
}
@@ -2121,6 +2119,13 @@
method protected void finalize();
method public void notifyEvent(@NonNull android.app.search.Query, @NonNull android.app.search.SearchTargetEvent);
method @Nullable public void query(@NonNull android.app.search.Query, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.search.SearchTarget>>);
+ method public void registerEmptyQueryResultUpdateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.app.search.SearchSession.Callback);
+ method public void requestEmptyQueryResultUpdate();
+ method public void unregisterEmptyQueryResultUpdateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.app.search.SearchSession.Callback);
+ }
+
+ public static interface SearchSession.Callback {
+ method public void onTargetsAvailable(@NonNull java.util.List<android.app.search.SearchTarget>);
}
public final class SearchSessionId implements android.os.Parcelable {
@@ -3232,10 +3237,8 @@
method public abstract boolean isCredentialProtectedStorage();
method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public android.content.Intent registerReceiverForAllUsers(@Nullable android.content.BroadcastReceiver, @NonNull android.content.IntentFilter, @Nullable String, @Nullable android.os.Handler);
method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public android.content.Intent registerReceiverForAllUsers(@Nullable android.content.BroadcastReceiver, @NonNull android.content.IntentFilter, @Nullable String, @Nullable android.os.Handler, int);
- method public abstract void sendBroadcast(android.content.Intent, @Nullable String, @Nullable android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle);
method public void sendBroadcastMultiplePermissions(@NonNull android.content.Intent, @NonNull String[], @Nullable android.app.BroadcastOptions);
- method public abstract void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @Nullable android.os.Bundle, @NonNull android.os.UserHandle);
field public static final String AMBIENT_CONTEXT_SERVICE = "ambient_context";
@@ -3298,9 +3301,7 @@
method public android.content.Context createCredentialProtectedStorageContext();
method @Nullable public java.io.File getPreloadsFileCache();
method public boolean isCredentialProtectedStorage();
- method public void sendBroadcast(android.content.Intent, @Nullable String, @Nullable android.os.Bundle);
method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle);
- method public void sendOrderedBroadcast(android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
}
public class Intent implements java.lang.Cloneable android.os.Parcelable {
@@ -3592,7 +3593,7 @@
}
public class PackageInstaller {
- method @NonNull public android.content.pm.PackageInstaller.InstallInfo getInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
+ method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean);
field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL";
@@ -3632,7 +3633,6 @@
method public boolean getAllocateAggressive();
method @Deprecated public boolean getAllowDowngrade();
method public int getAutoRevokePermissionsMode();
- method public boolean getDontKillApp();
method public boolean getEnableRollback();
method @Nullable public String[] getGrantedRuntimePermissions();
method public boolean getInstallAsFullApp(boolean);
@@ -3648,7 +3648,6 @@
method @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public void setAllocateAggressive(boolean);
method @Deprecated public void setAllowDowngrade(boolean);
method @RequiresPermission(allOf={android.Manifest.permission.INSTALL_PACKAGES, "com.android.permission.USE_INSTALLER_V2"}) public void setDataLoaderParams(@NonNull android.content.pm.DataLoaderParams);
- method public void setDontKillApp(boolean);
method public void setEnableRollback(boolean);
method public void setEnableRollback(boolean, int);
method @RequiresPermission(android.Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS) public void setGrantedRuntimePermissions(String[]);
@@ -7961,6 +7960,7 @@
public class MediaEvent extends android.media.tv.tuner.filter.FilterEvent {
method public long getAudioHandle();
+ method @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations();
method public long getAvDataId();
method public long getDataLength();
method public long getDts();
@@ -8911,7 +8911,6 @@
}
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();
@@ -8946,7 +8945,6 @@
}
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();
@@ -9941,6 +9939,7 @@
public final class BugreportManager {
method @RequiresPermission(android.Manifest.permission.DUMP) @WorkerThread public void preDumpUiData();
method @RequiresPermission(android.Manifest.permission.DUMP) public void requestBugreport(@NonNull android.os.BugreportParams, @Nullable CharSequence, @Nullable CharSequence);
+ method @RequiresPermission(android.Manifest.permission.DUMP) @WorkerThread public void retrieveBugreport(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback);
method @RequiresPermission(android.Manifest.permission.DUMP) @WorkerThread public void startBugreport(@NonNull android.os.ParcelFileDescriptor, @Nullable android.os.ParcelFileDescriptor, @NonNull android.os.BugreportParams, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback);
}
@@ -9949,6 +9948,7 @@
ctor public BugreportParams(int, int);
method public int getFlags();
method public int getMode();
+ field public static final int BUGREPORT_FLAG_DEFER_CONSENT = 2; // 0x2
field public static final int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = 1; // 0x1
field public static final int BUGREPORT_MODE_FULL = 0; // 0x0
field public static final int BUGREPORT_MODE_INTERACTIVE = 1; // 0x1
@@ -11910,9 +11910,10 @@
method public int onSwitchToSubscriptionWithPort(int, int, @Nullable String, boolean);
method public abstract int onUpdateSubscriptionNickname(int, String, String);
field public static final String ACTION_BIND_CARRIER_PROVISIONING_SERVICE = "android.service.euicc.action.BIND_CARRIER_PROVISIONING_SERVICE";
+ field @RequiresPermission(android.Manifest.permission.BIND_EUICC_SERVICE) public static final String ACTION_CONVERT_TO_EMBEDDED_SUBSCRIPTION = "android.service.euicc.action.CONVERT_TO_EMBEDDED_SUBSCRIPTION";
field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
- field public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
- field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
+ field @RequiresPermission(android.Manifest.permission.BIND_EUICC_SERVICE) public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
+ field @RequiresPermission(android.Manifest.permission.BIND_EUICC_SERVICE) public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
field @Deprecated public static final String ACTION_RESOLVE_CONFIRMATION_CODE = "android.service.euicc.action.RESOLVE_CONFIRMATION_CODE";
field public static final String ACTION_RESOLVE_DEACTIVATE_SIM = "android.service.euicc.action.RESOLVE_DEACTIVATE_SIM";
@@ -11921,6 +11922,7 @@
field public static final String ACTION_START_CARRIER_ACTIVATION = "android.service.euicc.action.START_CARRIER_ACTIVATION";
field public static final String ACTION_START_EUICC_ACTIVATION = "android.service.euicc.action.START_EUICC_ACTIVATION";
field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
+ field @RequiresPermission(android.Manifest.permission.BIND_EUICC_SERVICE) public static final String ACTION_TRANSFER_EMBEDDED_SUBSCRIPTIONS = "android.service.euicc.action.TRANSFER_EMBEDDED_SUBSCRIPTIONS";
field public static final String CATEGORY_EUICC_UI = "android.service.euicc.category.EUICC_UI";
field public static final String EUICC_SERVICE_INTERFACE = "android.service.euicc.EuiccService";
field public static final String EXTRA_RESOLUTION_ALLOW_POLICY_RULES = "android.service.euicc.extra.RESOLUTION_ALLOW_POLICY_RULES";
@@ -12273,7 +12275,11 @@
method @MainThread public abstract void onDestroy(@NonNull android.app.search.SearchSessionId);
method @MainThread public abstract void onNotifyEvent(@NonNull android.app.search.SearchSessionId, @NonNull android.app.search.Query, @NonNull android.app.search.SearchTargetEvent);
method @MainThread public abstract void onQuery(@NonNull android.app.search.SearchSessionId, @NonNull android.app.search.Query, @NonNull java.util.function.Consumer<java.util.List<android.app.search.SearchTarget>>);
+ method @MainThread public void onRequestEmptyQueryResultUpdate(@NonNull android.app.search.SearchSessionId);
method public void onSearchSessionCreated(@NonNull android.app.search.SearchContext, @NonNull android.app.search.SearchSessionId);
+ method @MainThread public void onStartUpdateEmptyQueryResult();
+ method @MainThread public void onStopUpdateEmptyQueryResult();
+ method public final void updateEmptyQueryResult(@NonNull android.app.search.SearchSessionId, @NonNull java.util.List<android.app.search.SearchTarget>);
}
}
@@ -14345,7 +14351,6 @@
field public static final String ACTION_SIM_SLOT_STATUS_CHANGED = "android.telephony.action.SIM_SLOT_STATUS_CHANGED";
field public static final int ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G = 3; // 0x3
field public static final int ALLOWED_NETWORK_TYPES_REASON_POWER = 1; // 0x1
- field public static final int ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS = 4; // 0x4
field public static final int CALL_WAITING_STATUS_DISABLED = 2; // 0x2
field public static final int CALL_WAITING_STATUS_ENABLED = 1; // 0x1
field public static final int CALL_WAITING_STATUS_FDN_CHECK_FAILURE = 5; // 0x5
@@ -14875,11 +14880,13 @@
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public boolean isSupportedCountry(@NonNull String);
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setSupportedCountries(@NonNull java.util.List<java.lang.String>);
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setUnsupportedCountries(@NonNull java.util.List<java.lang.String>);
+ field @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public static final String ACTION_CONVERT_TO_EMBEDDED_SUBSCRIPTION = "android.telephony.euicc.action.CONVERT_TO_EMBEDDED_SUBSCRIPTION";
field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
field @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public static final String ACTION_OTA_STATUS_CHANGED = "android.telephony.euicc.action.OTA_STATUS_CHANGED";
field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.telephony.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
+ field @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public static final String ACTION_TRANSFER_EMBEDDED_SUBSCRIPTIONS = "android.telephony.euicc.action.TRANSFER_EMBEDDED_SUBSCRIPTIONS";
field public static final int EUICC_ACTIVATION_TYPE_ACCOUNT_REQUIRED = 4; // 0x4
field public static final int EUICC_ACTIVATION_TYPE_BACKUP = 2; // 0x2
field public static final int EUICC_ACTIVATION_TYPE_DEFAULT = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 5f2f623..400a324 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -165,7 +165,7 @@
method @Nullable public String getIconResourcePackage();
}
- public class ActivityOptions {
+ public class ActivityOptions extends android.app.ComponentOptions {
method public boolean isEligibleForLegacyPermissionPrompt();
method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
method @NonNull @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) public static android.app.ActivityOptions makeCustomTaskAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
@@ -269,7 +269,7 @@
method public default void onOpActiveChanged(@NonNull String, int, @NonNull String, @Nullable String, boolean, int, int);
}
- public class BroadcastOptions {
+ public class BroadcastOptions extends android.app.ComponentOptions {
ctor public BroadcastOptions(@NonNull android.os.Bundle);
method @Deprecated public int getMaxManifestReceiverApiLevel();
method public long getTemporaryAppAllowlistDuration();
@@ -282,6 +282,12 @@
field public static final long CHANGE_ALWAYS_ENABLED = 209888056L; // 0xc82a338L
}
+ public class ComponentOptions {
+ field public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED = 1; // 0x1
+ field public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2; // 0x2
+ field public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED = 0; // 0x0
+ }
+
public class DownloadManager {
field public static final String COLUMN_MEDIASTORE_URI = "mediastore_uri";
}
@@ -464,8 +470,11 @@
public class WallpaperManager {
method @Nullable public android.graphics.Bitmap getBitmap();
+ method @Nullable public android.graphics.Bitmap getBitmapAsUser(int, boolean, int);
+ method public boolean isLockscreenLiveWallpaperEnabled();
method @Nullable public android.graphics.Rect peekBitmapDimensions();
method @Nullable public android.graphics.Rect peekBitmapDimensions(int);
+ method public void setWallpaperZoomOut(@NonNull android.os.IBinder, float);
method public boolean shouldEnableWideColorGamut();
method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int);
}
@@ -813,6 +822,7 @@
field public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 1.5f;
field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L
field public static final long OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN = 208648326L; // 0xc6fb886L
+ field public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // 0xe28701fL
field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
}
@@ -1869,7 +1879,10 @@
public class Build {
method public static boolean is64BitAbi(String);
method public static boolean isDebuggable();
+ field @Nullable public static final String BRAND_FOR_ATTESTATION;
field public static final boolean IS_EMULATOR;
+ field @Nullable public static final String MODEL_FOR_ATTESTATION;
+ field @Nullable public static final String PRODUCT_FOR_ATTESTATION;
}
public static class Build.VERSION {
@@ -2067,6 +2080,7 @@
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isUserTypeEnabled(@NonNull String);
+ method public boolean isVisibleBackgroundUsersOnDefaultDisplaySupported();
method public boolean isVisibleBackgroundUsersSupported();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
}
@@ -2395,6 +2409,8 @@
field public static final String DISABLE_WINDOW_BLURS = "disable_window_blurs";
field public static final String DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD = "dynamic_power_savings_disable_threshold";
field public static final String DYNAMIC_POWER_SAVINGS_ENABLED = "dynamic_power_savings_enabled";
+ field public static final String HDR_CONVERSION_MODE = "hdr_conversion_mode";
+ field public static final String HDR_FORCE_CONVERSION_TYPE = "hdr_force_conversion_type";
field public static final String HIDDEN_API_BLACKLIST_EXEMPTIONS = "hidden_api_blacklist_exemptions";
field public static final String HIDDEN_API_POLICY = "hidden_api_policy";
field public static final String HIDE_ERROR_DIALOGS = "hide_error_dialogs";
@@ -3141,7 +3157,9 @@
}
@UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
+ method public void getBoundsOnScreen(@NonNull android.graphics.Rect, boolean);
method public android.view.View getTooltipView();
+ method public void getWindowDisplayFrame(@NonNull android.graphics.Rect);
method public boolean isAutofilled();
method public static boolean isDefaultFocusHighlightEnabled();
method public boolean isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index e849cdbc..4a97280 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -855,6 +855,10 @@
New setting keys are not allowed (Field: DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD); use getters/setters in relevant manager class
NoSettingsProvider: android.provider.Settings.Global#DYNAMIC_POWER_SAVINGS_ENABLED:
New setting keys are not allowed (Field: DYNAMIC_POWER_SAVINGS_ENABLED); use getters/setters in relevant manager class
+NoSettingsProvider: android.provider.Settings.Global#HDR_CONVERSION_MODE:
+ New setting keys are not allowed (Field: HDR_CONVERSION_MODE); use getters/setters in relevant manager class
+NoSettingsProvider: android.provider.Settings.Global#HDR_FORCE_CONVERSION_TYPE:
+ New setting keys are not allowed (Field: HDR_FORCE_CONVERSION_TYPE); use getters/setters in relevant manager class
NoSettingsProvider: android.provider.Settings.Global#HIDDEN_API_BLACKLIST_EXEMPTIONS:
New setting keys are not allowed (Field: HIDDEN_API_BLACKLIST_EXEMPTIONS); use getters/setters in relevant manager class
NoSettingsProvider: android.provider.Settings.Global#HIDDEN_API_POLICY:
diff --git a/core/java/android/accessibilityservice/MagnificationConfig.java b/core/java/android/accessibilityservice/MagnificationConfig.java
index ddafb22..486dc50 100644
--- a/core/java/android/accessibilityservice/MagnificationConfig.java
+++ b/core/java/android/accessibilityservice/MagnificationConfig.java
@@ -76,6 +76,7 @@
}
private int mMode = MAGNIFICATION_MODE_DEFAULT;
+ private boolean mActivated = false;
private float mScale = Float.NaN;
private float mCenterX = Float.NaN;
private float mCenterY = Float.NaN;
@@ -86,6 +87,7 @@
private MagnificationConfig(@NonNull Parcel parcel) {
mMode = parcel.readInt();
+ mActivated = parcel.readBoolean();
mScale = parcel.readFloat();
mCenterX = parcel.readFloat();
mCenterY = parcel.readFloat();
@@ -102,9 +104,20 @@
}
/**
+ * Returns the activated state of the controlling magnifier. The controlling magnifier can be
+ * activated even if the scale returned by {@link MagnificationConfig#getScale()} equals to 1.0.
+ *
+ * @return {@code true} if the magnifier is showing on screen,
+ * {@code false} otherwise.
+ */
+ public boolean isActivated() {
+ return mActivated;
+ }
+
+ /**
* Returns the magnification scale of the controlling magnifier
*
- * @return the scale If the controlling magnifier is not activated, it returns 1 by default
+ * @return The magnification scale
*/
public float getScale() {
return mScale;
@@ -113,9 +126,7 @@
/**
* Returns the screen-relative X coordinate of the center of the magnification viewport.
*
- * @return the X coordinate. If the controlling magnifier is {@link #MAGNIFICATION_MODE_WINDOW}
- * but not enabled, it returns {@link Float#NaN}. If the controlling magnifier is {@link
- * #MAGNIFICATION_MODE_FULLSCREEN} but not enabled, it returns 0
+ * @return The X coordinate
*/
public float getCenterX() {
return mCenterX;
@@ -124,9 +135,7 @@
/**
* Returns the screen-relative Y coordinate of the center of the magnification viewport.
*
- * @return the Y coordinate If the controlling magnifier is {@link #MAGNIFICATION_MODE_WINDOW}
- * but not enabled, it returns {@link Float#NaN}. If the controlling magnifier is {@link
- * #MAGNIFICATION_MODE_FULLSCREEN} but not enabled, it returns 0
+ * @return The Y coordinate
*/
public float getCenterY() {
return mCenterY;
@@ -138,6 +147,8 @@
StringBuilder stringBuilder = new StringBuilder("MagnificationConfig[");
stringBuilder.append("mode: ").append(getMode());
stringBuilder.append(", ");
+ stringBuilder.append("activated: ").append(isActivated());
+ stringBuilder.append(", ");
stringBuilder.append("scale: ").append(getScale());
stringBuilder.append(", ");
stringBuilder.append("centerX: ").append(getCenterX());
@@ -155,6 +166,7 @@
@Override
public void writeToParcel(@NonNull Parcel parcel, int flags) {
parcel.writeInt(mMode);
+ parcel.writeBoolean(mActivated);
parcel.writeFloat(mScale);
parcel.writeFloat(mCenterX);
parcel.writeFloat(mCenterY);
@@ -166,6 +178,7 @@
public static final class Builder {
private int mMode = MAGNIFICATION_MODE_DEFAULT;
+ private boolean mActivated = true;
private float mScale = Float.NaN;
private float mCenterX = Float.NaN;
private float mCenterY = Float.NaN;
@@ -189,6 +202,18 @@
}
/**
+ * Sets magnification activated state.
+ *
+ * @param activated The magnification activated state
+ * @return This builder
+ */
+ @NonNull
+ public MagnificationConfig.Builder setActivated(boolean activated) {
+ mActivated = activated;
+ return this;
+ }
+
+ /**
* Sets the magnification scale.
*
* @param scale The magnification scale, in the range [1, 8]
@@ -237,6 +262,7 @@
public MagnificationConfig build() {
MagnificationConfig magnificationConfig = new MagnificationConfig();
magnificationConfig.mMode = mMode;
+ magnificationConfig.mActivated = mActivated;
magnificationConfig.mScale = mScale;
magnificationConfig.mCenterX = mCenterX;
magnificationConfig.mCenterY = mCenterY;
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 2a390a7..b57fb20 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -88,6 +88,16 @@
*/
public static final String EXTRA_USAGE_TIME_REPORT_PACKAGES = "android.usage_time_packages";
+ /** No explicit value chosen. The system will decide whether to grant privileges. */
+ public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED =
+ ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+ /** Allow the {@link PendingIntent} to use the background activity start privileges. */
+ public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED =
+ ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+ /** Deny the {@link PendingIntent} to use the background activity start privileges. */
+ public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED =
+ ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
+
/**
* The package name that created the options.
* @hide
@@ -2430,6 +2440,30 @@
return super.getPendingIntentBackgroundActivityStartMode();
}
+ /**
+ * Set PendingIntent activity is allowed to be started in the background if the caller
+ * can start background activities.
+ *
+ * @deprecated use #setPendingIntentBackgroundActivityStartMode(int) to set the full range
+ * of states
+ */
+ @Override
+ @Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
+ super.setPendingIntentBackgroundActivityLaunchAllowed(allowed);
+ }
+
+ /**
+ * Get PendingIntent activity is allowed to be started in the background if the caller can start
+ * background activities.
+ *
+ * @deprecated use {@link #getPendingIntentBackgroundActivityStartMode()} since for apps
+ * targeting {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or higher this value might
+ * not match the actual behavior if the value was not explicitly set.
+ */
+ @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
+ return super.isPendingIntentBackgroundActivityLaunchAllowed();
+ }
+
/** @hide */
@Override
public String toString() {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3761251..170c0b4 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -785,9 +785,10 @@
static final class ReceiverData extends BroadcastReceiver.PendingResult {
public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras,
boolean ordered, boolean sticky, boolean assumeDelivered, IBinder token,
- int sendingUser) {
+ int sendingUser, int sentFromUid, String sentFromPackage) {
super(resultCode, resultData, resultExtras, TYPE_COMPONENT, ordered, sticky,
- assumeDelivered, token, sendingUser, intent.getFlags());
+ assumeDelivered, token, sendingUser, intent.getFlags(), sentFromUid,
+ sentFromPackage);
this.intent = intent;
}
@@ -801,7 +802,8 @@
return "ReceiverData{intent=" + intent + " packageName=" +
info.packageName + " resultCode=" + getResultCode()
+ " resultData=" + getResultData() + " resultExtras="
- + getResultExtras(false) + "}";
+ + getResultExtras(false) + " sentFromUid="
+ + getSentFromUid() + " sentFromPackage=" + getSentFromPackage() + "}";
}
}
@@ -1041,10 +1043,12 @@
public final void scheduleReceiver(Intent intent, ActivityInfo info,
CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
- boolean ordered, boolean assumeDelivered, int sendingUser, int processState) {
+ boolean ordered, boolean assumeDelivered, int sendingUser, int processState,
+ int sentFromUid, String sentFromPackage) {
updateProcessState(processState, false);
ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
- ordered, false, assumeDelivered, mAppThread.asBinder(), sendingUser);
+ ordered, false, assumeDelivered, mAppThread.asBinder(), sendingUser,
+ sentFromUid, sentFromPackage);
r.info = info;
sendMessage(H.RECEIVER, r);
}
@@ -1055,11 +1059,13 @@
if (r.registered) {
scheduleRegisteredReceiver(r.receiver, r.intent,
r.resultCode, r.data, r.extras, r.ordered, r.sticky,
- r.assumeDelivered, r.sendingUser, r.processState);
+ r.assumeDelivered, r.sendingUser, r.processState,
+ r.sentFromUid, r.sentFromPackage);
} else {
scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
r.resultCode, r.data, r.extras, r.sync,
- r.assumeDelivered, r.sendingUser, r.processState);
+ r.assumeDelivered, r.sendingUser, r.processState,
+ r.sentFromUid, r.sentFromPackage);
}
}
}
@@ -1289,7 +1295,8 @@
// applies transaction ordering per object for such calls.
public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
int resultCode, String dataStr, Bundle extras, boolean ordered,
- boolean sticky, boolean assumeDelivered, int sendingUser, int processState)
+ boolean sticky, boolean assumeDelivered, int sendingUser, int processState,
+ int sentFromUid, String sentFromPackage)
throws RemoteException {
updateProcessState(processState, false);
@@ -1299,12 +1306,19 @@
// report an expected delivery event
if (receiver instanceof LoadedApk.ReceiverDispatcher.InnerReceiver) {
((LoadedApk.ReceiverDispatcher.InnerReceiver) receiver).performReceive(intent,
- resultCode, dataStr, extras, ordered, sticky, assumeDelivered, sendingUser);
+ resultCode, dataStr, extras, ordered, sticky, assumeDelivered, sendingUser,
+ sentFromUid, sentFromPackage);
} else {
if (!assumeDelivered) {
Log.wtf(TAG, "scheduleRegisteredReceiver() called for " + receiver
+ " and " + intent + " without mechanism to finish delivery");
}
+ if (sentFromUid != Process.INVALID_UID || sentFromPackage != null) {
+ Log.wtf(TAG,
+ "scheduleRegisteredReceiver() called for " + receiver + " and " + intent
+ + " from " + sentFromPackage + " (UID: " + sentFromUid
+ + ") without mechanism to propagate the sender's identity");
+ }
receiver.performReceive(intent, resultCode, dataStr, extras, ordered, sticky,
sendingUser);
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index f9e4081..73f34eb 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -42,7 +42,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.database.DatabaseUtils;
-import android.healthconnect.HealthConnectManager;
+import android.health.connect.HealthConnectManager;
import android.media.AudioAttributes.AttributeUsage;
import android.os.Binder;
import android.os.Build;
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 88765c3..f6992c9 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -27,6 +27,7 @@
import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
import android.compat.annotation.EnabledSince;
+import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
@@ -46,9 +47,7 @@
* Helper class for building an options Bundle that can be used with
* {@link android.content.Context#sendBroadcast(android.content.Intent)
* Context.sendBroadcast(Intent)} and related methods.
- * {@hide}
*/
-@SystemApi
public class BroadcastOptions extends ComponentOptions {
private long mTemporaryAppAllowlistDuration;
private @TempAllowListType int mTemporaryAppAllowlistType;
@@ -64,6 +63,7 @@
private boolean mRequireCompatChangeEnabled = true;
private boolean mIsAlarmBroadcast = false;
private boolean mIsDeferUntilActive = false;
+ private boolean mShareIdentity = false;
private long mIdForResponseEvent;
private @Nullable IntentFilter mRemoveMatchingFilter;
private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
@@ -172,6 +172,12 @@
"android:broadcast.is_alarm";
/**
+ * Whether the broadcasting app's identity should be available to the receiver.
+ * @see #setShareIdentityEnabled(boolean)
+ */
+ private static final String KEY_SHARE_IDENTITY = "android:broadcast.share_identity";
+
+ /**
* @hide
* @deprecated Use {@link android.os.PowerExemptionManager#
* TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED} instead.
@@ -270,7 +276,12 @@
*/
public static final int DELIVERY_GROUP_POLICY_MERGED = 2;
- public static BroadcastOptions makeBasic() {
+ /**
+ * Creates a basic {@link BroadcastOptions} with no options initially set.
+ *
+ * @return an instance of {@code BroadcastOptions} against which options can be set
+ */
+ public static @NonNull BroadcastOptions makeBasic() {
BroadcastOptions opts = new BroadcastOptions();
return opts;
}
@@ -318,6 +329,7 @@
mRequireCompatChangeEnabled = opts.getBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, true);
mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
mIsAlarmBroadcast = opts.getBoolean(KEY_ALARM_BROADCAST, false);
+ mShareIdentity = opts.getBoolean(KEY_SHARE_IDENTITY, false);
mRemoveMatchingFilter = opts.getParcelable(KEY_REMOVE_MATCHING_FILTER,
IntentFilter.class);
mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
@@ -335,8 +347,10 @@
* power allowlist when this broadcast is being delivered to it.
* @param duration The duration in milliseconds; 0 means to not place on allowlist.
* @deprecated use {@link #setTemporaryAppAllowlist(long, int, int, String)} instead.
+ * @hide
*/
@Deprecated
+ @SystemApi
@RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND})
@@ -350,6 +364,8 @@
* Set a duration for which the system should temporary place an application on the
* power allowlist when this broadcast is being delivered to it, specify the temp allowlist
* type.
+ * @hide
+ *
* @param duration the duration in milliseconds.
* 0 means to not place on allowlist, and clears previous call to this method.
* @param type one of {@link TempAllowListType}.
@@ -360,6 +376,7 @@
* @param reason A human-readable reason explaining why the app is temp allowlisted. Only
* used for logging purposes. Could be null or empty string.
*/
+ @SystemApi
@RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND})
@@ -500,7 +517,9 @@
* Sets whether pending intent can be sent for an application with background restrictions
* @param dontSendToRestrictedApps if true, pending intent will not be sent for an application
* with background restrictions. Default value is {@code false}
+ * @hide
*/
+ @SystemApi
public void setDontSendToRestrictedApps(boolean dontSendToRestrictedApps) {
mDontSendToRestrictedApps = dontSendToRestrictedApps;
}
@@ -516,7 +535,9 @@
/**
* Sets the process will be able to start activities from background for the duration of
* the broadcast dispatch. Default value is {@code false}
+ * @hide
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND)
public void setBackgroundActivityStartsAllowed(boolean allowBackgroundActivityStarts) {
mAllowBackgroundActivityStarts = allowBackgroundActivityStarts;
@@ -578,6 +599,7 @@
* <p>
* This requirement applies to both manifest registered and runtime
* registered receivers.
+ * @hide
*
* @param changeId the {@link ChangeId} to inspect
* @param enabled the required enabled state of the inspected
@@ -585,6 +607,7 @@
* @see CompatChanges#isChangeEnabled
* @see #clearRequireCompatChange()
*/
+ @SystemApi
public void setRequireCompatChange(long changeId, boolean enabled) {
mRequireCompatChangeId = changeId;
mRequireCompatChangeEnabled = enabled;
@@ -593,7 +616,9 @@
/**
* Clear any previously defined requirement for this broadcast requested via
* {@link #setRequireCompatChange(long, boolean)}.
+ * @hide
*/
+ @SystemApi
public void clearRequireCompatChange() {
mRequireCompatChangeId = CHANGE_INVALID;
mRequireCompatChangeEnabled = true;
@@ -621,6 +646,39 @@
}
/**
+ * Sets whether the identity of the broadcasting app should be shared with all receivers
+ * that will receive this broadcast.
+ *
+ * <p>Use this option when broadcasting to a receiver that needs to know the identity of the
+ * broadcaster; with this set to {@code true}, the receiver will have access to the broadcasting
+ * app's package name and uid.
+ *
+ * <p>Defaults to {@code false} if not set.
+ *
+ * @param shareIdentityEnabled whether the broadcasting app's identity should be shared with the
+ * receiver
+ * @return {@code this} {@link BroadcastOptions} instance
+ * @see BroadcastReceiver#getSentFromUid()
+ * @see BroadcastReceiver#getSentFromPackage()
+ */
+ public @NonNull BroadcastOptions setShareIdentityEnabled(boolean shareIdentityEnabled) {
+ mShareIdentity = shareIdentityEnabled;
+ return this;
+ }
+
+ /**
+ * Returns whether the broadcasting app has opted-in to sharing its identity with the receiver.
+ *
+ * @return {@code true} if the broadcasting app has opted in to sharing its identity
+ * @see #setShareIdentityEnabled(boolean)
+ * @see BroadcastReceiver#getSentFromUid()
+ * @see BroadcastReceiver#getSentFromPackage()
+ */
+ public boolean isShareIdentityEnabled() {
+ return mShareIdentity;
+ }
+
+ /**
* Did this broadcast originate from a push message from the server?
*
* @return true if this broadcast is a push message, false otherwise.
@@ -989,7 +1047,7 @@
* extras merger is supplied.
*/
@Override
- public Bundle toBundle() {
+ public @NonNull Bundle toBundle() {
Bundle b = super.toBundle();
if (isTemporaryAppAllowlistSet()) {
b.putLong(KEY_TEMPORARY_APP_ALLOWLIST_DURATION, mTemporaryAppAllowlistDuration);
@@ -1000,6 +1058,9 @@
if (mIsAlarmBroadcast) {
b.putBoolean(KEY_ALARM_BROADCAST, true);
}
+ if (mShareIdentity) {
+ b.putBoolean(KEY_SHARE_IDENTITY, true);
+ }
if (mMinManifestReceiverApiLevel != 0) {
b.putInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, mMinManifestReceiverApiLevel);
}
diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java
index 3776369..9c262b9 100644
--- a/core/java/android/app/ComponentOptions.java
+++ b/core/java/android/app/ComponentOptions.java
@@ -20,6 +20,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
import android.os.Bundle;
import java.lang.annotation.Retention;
@@ -29,6 +31,10 @@
* Base class for {@link ActivityOptions} and {@link BroadcastOptions}.
* @hide
*/
+// Expose the methods and constants required to test the SystemApis in subclasses.
+@TestApi
+// Suppressed since lint is recommending class have a suffix of Params.
+@SuppressLint("UserHandleName")
public class ComponentOptions {
/**
@@ -57,17 +63,30 @@
private boolean mPendingIntentBalAllowedByPermission = false;
private boolean mIsInteractive = false;
+ /** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"MODE_BACKGROUND_ACTIVITY_START_"}, value = {
MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED,
MODE_BACKGROUND_ACTIVITY_START_ALLOWED,
MODE_BACKGROUND_ACTIVITY_START_DENIED})
public @interface BackgroundActivityStartMode {}
- /** No explicit value chosen. The system will decide whether to grant privileges. */
+ /**
+ * No explicit value chosen. The system will decide whether to grant privileges.
+ * @hide
+ */
+ @TestApi
public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED = 0;
- /** Allow the {@link PendingIntent} to use the background activity start privileges. */
+ /**
+ * Allow the {@link PendingIntent} to use the background activity start privileges.
+ * @hide
+ */
+ @TestApi
public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED = 1;
- /** Deny the {@link PendingIntent} to use the background activity start privileges. */
+ /**
+ * Deny the {@link PendingIntent} to use the background activity start privileges.
+ * @hide
+ */
+ @TestApi
public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2;
ComponentOptions() {
@@ -118,6 +137,7 @@
*
* @deprecated use #setPendingIntentBackgroundActivityStartMode(int) to set the full range
* of states
+ * @hide
*/
@Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
mPendingIntentBalAllowed = allowed;
@@ -130,6 +150,7 @@
* @deprecated use {@link #getPendingIntentBackgroundActivityStartMode()} since for apps
* targeting {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or higher this value might
* not match the actual behavior if the value was not explicitly set.
+ * @hide
*/
@Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
if (mPendingIntentBalAllowed == null) {
@@ -147,6 +168,7 @@
* methods. A privileged sender of a PendingIntent should only grant
* {@link #MODE_BACKGROUND_ACTIVITY_START_ALLOWED} if the PendingIntent is from a trusted source
* and/or executed on behalf the user.
+ * @hide
*/
public @NonNull ComponentOptions setPendingIntentBackgroundActivityStartMode(
@BackgroundActivityStartMode int state) {
@@ -169,6 +191,7 @@
/**
* Gets the mode for allowing or denying the senders privileges to start background activities
* to the PendingIntent.
+ * @hide
*
* @see #setPendingIntentBackgroundActivityStartMode(int)
*/
@@ -199,6 +222,7 @@
return mPendingIntentBalAllowedByPermission;
}
+ /** @hide */
public Bundle toBundle() {
Bundle b = new Bundle();
if (mPendingIntentBalAllowed != null) {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 12899f2..bed75db 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -25,6 +25,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.UiContext;
import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.UnsupportedAppUsage;
@@ -1358,7 +1359,13 @@
}
@Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public void sendOrderedBroadcast(Intent intent, String receiverPermission) {
+ sendOrderedBroadcast(intent, receiverPermission, /*options=*/ null);
+ }
+
+ @Override
+ public void sendOrderedBroadcast(Intent intent, String receiverPermission, Bundle options) {
warnIfCallingFromSystemProcess();
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
String[] receiverPermissions = receiverPermission == null ? null
@@ -1368,8 +1375,8 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, receiverPermissions,
- null /*excludedPermissions=*/, null, AppOpsManager.OP_NONE, null, true, false,
- getUserId());
+ null /*excludedPermissions=*/, null, AppOpsManager.OP_NONE, options, true,
+ false, getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index 20d19c1..d8eb03e 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -109,7 +109,7 @@
*/
// TODO (b/254661666): Change to @EnabledAfter(T)
@ChangeId
- @Disabled
+ @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
@Overridable
public static final long FGS_TYPE_NONE_DISABLED_CHANGE_ID = 255038118L;
@@ -144,7 +144,7 @@
*/
// TODO (b/254661666): Change to @EnabledAfter(T)
@ChangeId
- @Disabled
+ @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
@Overridable
public static final long FGS_TYPE_PERMISSION_CHANGE_ID = 254662522L;
@@ -1059,7 +1059,7 @@
if (policy.isTypeDisabled(callerUid)) {
return FGS_TYPE_POLICY_CHECK_DISABLED;
}
- int permissionResult = PERMISSION_DENIED;
+ int permissionResult = PERMISSION_GRANTED;
// Do we have the permission to start FGS with this type.
if (policy.mAllOfPermissions != null) {
permissionResult = policy.mAllOfPermissions.checkPermissions(context,
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 4f69d85..dad9b43 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -66,7 +66,8 @@
void scheduleReceiver(in Intent intent, in ActivityInfo info,
in CompatibilityInfo compatInfo,
int resultCode, in String data, in Bundle extras, boolean ordered,
- boolean assumeDelivered, int sendingUser, int processState);
+ boolean assumeDelivered, int sendingUser, int processState, int sentFromUid,
+ in String sentFromPackage);
void scheduleReceiverList(in List<ReceiverInfo> info);
@@ -102,7 +103,8 @@
in String[] args);
void scheduleRegisteredReceiver(IIntentReceiver receiver, in Intent intent,
int resultCode, in String data, in Bundle extras, boolean ordered,
- boolean sticky, boolean assumeDelivered, int sendingUser, int processState);
+ boolean sticky, boolean assumeDelivered, int sendingUser, int processState,
+ int sentFromUid, in String sentFromPackage);
void scheduleLowMemory();
void profilerControl(boolean start, in ProfilerInfo profilerInfo, int profileType);
void setSchedulingGroup(int group);
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 7c22902..c13da0b 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1683,12 +1683,13 @@
performReceive(intent, resultCode, data, extras, ordered, sticky,
BroadcastReceiver.PendingResult.guessAssumeDelivered(
BroadcastReceiver.PendingResult.TYPE_REGISTERED, ordered),
- sendingUser);
+ sendingUser, /*sentFromUid=*/ Process.INVALID_UID,
+ /*sentFromPackage=*/ null);
}
public void performReceive(Intent intent, int resultCode, String data,
Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
- int sendingUser) {
+ int sendingUser, int sentFromUid, String sentFromPackage) {
final LoadedApk.ReceiverDispatcher rd;
if (intent == null) {
Log.wtf(TAG, "Null intent received");
@@ -1703,7 +1704,8 @@
}
if (rd != null) {
rd.performReceive(intent, resultCode, data, extras,
- ordered, sticky, assumeDelivered, sendingUser);
+ ordered, sticky, assumeDelivered, sendingUser,
+ sentFromUid, sentFromPackage);
} else if (!assumeDelivered) {
// The activity manager dispatched a broadcast to a registered
// receiver in this process, but before it could be delivered the
@@ -1743,11 +1745,12 @@
private boolean mRunCalled;
public Args(Intent intent, int resultCode, String resultData, Bundle resultExtras,
- boolean ordered, boolean sticky, boolean assumeDelivered, int sendingUser) {
+ boolean ordered, boolean sticky, boolean assumeDelivered, int sendingUser,
+ int sentFromUid, String sentFromPackage) {
super(resultCode, resultData, resultExtras,
mRegistered ? TYPE_REGISTERED : TYPE_UNREGISTERED, ordered,
sticky, assumeDelivered, mAppThread.asBinder(), sendingUser,
- intent.getFlags());
+ intent.getFlags(), sentFromUid, sentFromPackage);
mCurIntent = intent;
}
@@ -1871,9 +1874,9 @@
public void performReceive(Intent intent, int resultCode, String data,
Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
- int sendingUser) {
+ int sendingUser, int sentFromUid, String sentFromPackage) {
final Args args = new Args(intent, resultCode, data, extras, ordered,
- sticky, assumeDelivered, sendingUser);
+ sticky, assumeDelivered, sendingUser, sentFromUid, sentFromPackage);
if (intent == null) {
Log.wtf(TAG, "Null intent received");
} else {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 9b348fc..1345910 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3885,8 +3885,11 @@
}
if (mN.extras.containsKey(EXTRA_PEOPLE_LIST)) {
- ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST, android.app.Person.class);
- mPersonList.addAll(people);
+ ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST,
+ android.app.Person.class);
+ if (people != null && !people.isEmpty()) {
+ mPersonList.addAll(people);
+ }
}
if (mN.getSmallIcon() == null && mN.icon != 0) {
diff --git a/core/java/android/app/ReceiverInfo.aidl b/core/java/android/app/ReceiverInfo.aidl
index 8d7e3c4..7364d0f 100644
--- a/core/java/android/app/ReceiverInfo.aidl
+++ b/core/java/android/app/ReceiverInfo.aidl
@@ -38,6 +38,8 @@
int sendingUser;
int processState;
int resultCode;
+ int sentFromUid = -1;
+ String sentFromPackage;
/**
* True if this instance represents a registered receiver and false if this instance
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index e485397..a155457 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -1140,6 +1140,22 @@
* Callback called on timeout for {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}.
* See {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE} for more details.
*
+ * <p>If the foreground service of type
+ * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}
+ * doesn't finish even after it's timed out,
+ * the app will be declared an ANR after a short grace period of several seconds.
+ *
+ * <p>Note, even though
+ * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}
+ * was added
+ * on Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * it can be also used on
+ * on prior android versions (just like other new foreground service types can be used).
+ * However, because {@link android.app.Service#onTimeout(int)} did not exist on prior versions,
+ * it will never called on such versions.
+ * Because of this, developers must make sure to stop the foreground service even if
+ * {@link android.app.Service#onTimeout(int)} is not called on such versions.
+ *
* @param startId the startId passed to {@link #onStartCommand(Intent, int, int)} when
* the service started.
*/
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 1c1a558..9e31011 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -30,6 +30,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.graphics.drawable.Icon;
import android.media.INearbyMediaDevicesProvider;
import android.media.INearbyMediaDevicesUpdateCallback;
@@ -47,6 +48,7 @@
import android.util.Slog;
import android.view.View;
+import com.android.internal.statusbar.AppClipsServiceConnector;
import com.android.internal.statusbar.IAddTileResultCallback;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.IUndoMediaTransferCallback;
@@ -1190,6 +1192,37 @@
return CompatChanges.isChangeEnabled(MEDIA_CONTROL_SESSION_ACTIONS, packageName, user);
}
+ /**
+ * Checks whether the supplied activity can {@link Activity#startActivityForResult(Intent, int)}
+ * a system activity that captures content on the screen to take a screenshot.
+ *
+ * <p>Note: The result should not be cached.
+ *
+ * <p>The system activity displays an editing tool that allows user to edit the screenshot, save
+ * it on device, and return the edited screenshot as {@link android.net.Uri} to the calling
+ * activity. User interaction is required to return the edited screenshot to the calling
+ * activity.
+ *
+ * <p>When {@code true}, callers can use {@link Activity#startActivityForResult(Intent, int)}
+ * to start start the content capture activity using
+ * {@link Intent#ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE}.
+ *
+ * @param activity Calling activity
+ * @return true if the activity supports launching the capture content activity for note.
+ *
+ * @see Intent#ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE
+ * @see Manifest.permission#LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE
+ * @see android.app.role.RoleManager#ROLE_NOTES
+ */
+ @RequiresPermission(Manifest.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE)
+ public boolean canLaunchCaptureContentActivityForNote(@NonNull Activity activity) {
+ Objects.requireNonNull(activity);
+ IBinder activityToken = activity.getActivityToken();
+ int taskId = ActivityClient.getInstance().getTaskForActivity(activityToken, false);
+ return new AppClipsServiceConnector(mContext)
+ .canLaunchCaptureContentActivityForNote(taskId);
+ }
+
/** @hide */
public static String windowStateToString(int state) {
if (state == WINDOW_STATE_HIDING) return "WINDOW_STATE_HIDING";
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 52d1d68..031d351 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -116,7 +116,7 @@
import android.hardware.radio.RadioManager;
import android.hardware.usb.IUsbManager;
import android.hardware.usb.UsbManager;
-import android.healthconnect.HealthServicesInitializer;
+import android.health.connect.HealthServicesInitializer;
import android.location.CountryDetector;
import android.location.ICountryDetector;
import android.location.ILocationManager;
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index 067a4c3..a34a50c 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -27,6 +27,7 @@
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.SystemProperties;
import android.util.Log;
import android.util.MathUtils;
import android.util.Size;
@@ -101,11 +102,13 @@
// Decides when dark theme is optimal for this wallpaper
private static final float DARK_THEME_MEAN_LUMINANCE = 0.3f;
// Minimum mean luminosity that an image needs to have to support dark text
- private static final float BRIGHT_IMAGE_MEAN_LUMINANCE = 0.7f;
+ private static final float BRIGHT_IMAGE_MEAN_LUMINANCE = SystemProperties.getInt(
+ "persist.wallpapercolors.threshold", 70) / 100f;
// We also check if the image has dark pixels in it,
// to avoid bright images with some dark spots.
private static final float DARK_PIXEL_CONTRAST = 5.5f;
- private static final float MAX_DARK_AREA = 0.05f;
+ private static final float MAX_DARK_AREA = SystemProperties.getInt(
+ "persist.wallpapercolors.max_dark_area", 5) / 100f;
private final List<Color> mMainColors;
private final Map<Integer, Integer> mAllColors;
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index f3a83d8..ff17824 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -784,6 +784,7 @@
* @return true if the lockscreen wallpaper always uses a wallpaperService, not a static image
* @hide
*/
+ @TestApi
public boolean isLockscreenLiveWallpaperEnabled() {
return mLockscreenLiveWallpaper;
}
@@ -1258,6 +1259,8 @@
* @param which Specifies home or lock screen
* @hide
*/
+ @TestApi
+ @Nullable
public Bitmap getBitmapAsUser(int userId, boolean hardware, @SetWallpaperFlags int which) {
final ColorManagementProxy cmProxy = getColorManagementProxy();
return sGlobals.peekWallpaperBitmap(mContext, true, which, userId, hardware, cmProxy);
@@ -1496,6 +1499,13 @@
/**
* Returns the information about the home screen wallpaper if its current wallpaper is a live
* wallpaper component. Otherwise, if the wallpaper is a static image, this returns null.
+ *
+ * <p>
+ * In order to use this, apps should declare a {@code <queries>} tag with the action
+ * {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
+ * this method will return {@code null} if the caller doesn't otherwise have
+ * <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package.
+ * </p>
*/
public WallpaperInfo getWallpaperInfo() {
return getWallpaperInfoForUser(mContext.getUserId());
@@ -1517,6 +1527,13 @@
* wallpaper component. Otherwise, if the wallpaper is a static image or is not set, this
* returns null.
*
+ * <p>
+ * In order to use this, apps should declare a {@code <queries>} tag with the action
+ * {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
+ * this method will return {@code null} if the caller doesn't otherwise have
+ * <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package.
+ * </p>
+ *
* @param which Specifies wallpaper to request (home or lock).
* @throws IllegalArgumentException if {@code which} is not exactly one of
* {{@link #FLAG_SYSTEM},{@link #FLAG_LOCK}}.
@@ -2421,6 +2438,7 @@
*
* @hide
*/
+ @TestApi
public void setWallpaperZoomOut(@NonNull IBinder windowToken, float zoom) {
if (zoom < 0 || zoom > 1f) {
throw new IllegalArgumentException("zoom must be between 0 and 1: " + zoom);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6e6d4e3..d8ec7cc 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3961,6 +3961,7 @@
*
* @throws SecurityException if caller is not device owner or profile owner of org-owned device
* or if called on a parent instance
+ * @throws UnsupportedOperationException if the device does not support MTE
* @param policy the MTE policy to be set
*/
public void setMtePolicy(@MtePolicy int policy) {
diff --git a/core/java/android/app/admin/PreferentialNetworkServiceConfig.java b/core/java/android/app/admin/PreferentialNetworkServiceConfig.java
index 0513099..4c97efb 100644
--- a/core/java/android/app/admin/PreferentialNetworkServiceConfig.java
+++ b/core/java/android/app/admin/PreferentialNetworkServiceConfig.java
@@ -544,9 +544,9 @@
pw.print("blockNonMatchingNetworks=");
pw.println(mShouldBlockNonMatchingNetworks);
pw.print("includedUids=");
- pw.println(mIncludedUids);
+ pw.println(Arrays.toString(mIncludedUids));
pw.print("excludedUids=");
- pw.println(mExcludedUids);
+ pw.println(Arrays.toString(mExcludedUids));
}
@Override
diff --git a/core/java/android/app/admin/SystemUpdatePolicy.java b/core/java/android/app/admin/SystemUpdatePolicy.java
index b100eb2..c760298 100644
--- a/core/java/android/app/admin/SystemUpdatePolicy.java
+++ b/core/java/android/app/admin/SystemUpdatePolicy.java
@@ -50,8 +50,8 @@
/**
* Determines when over-the-air system updates are installed on a device. Only a device policy
- * controller (DPC) running in device owner mode can set an update policy for the device—by calling
- * the {@code DevicePolicyManager} method
+ * controller (DPC) running in device owner mode or in profile owner mode for an organization-owned
+ * device can set an update policy for the device—by calling the {@code DevicePolicyManager} method
* {@link DevicePolicyManager#setSystemUpdatePolicy setSystemUpdatePolicy()}. An update
* policy affects the pending system update (if there is one) and any future updates for the device.
*
diff --git a/core/java/android/app/search/ISearchUiManager.aidl b/core/java/android/app/search/ISearchUiManager.aidl
index a298a2c..fefbd5a 100644
--- a/core/java/android/app/search/ISearchUiManager.aidl
+++ b/core/java/android/app/search/ISearchUiManager.aidl
@@ -36,5 +36,11 @@
void notifyEvent(in SearchSessionId sessionId, in Query input, in SearchTargetEvent event);
+ void registerEmptyQueryResultUpdateCallback(in SearchSessionId sessionId, in ISearchCallback callback);
+
+ void requestEmptyQueryResultUpdate(in SearchSessionId sessionId);
+
+ void unregisterEmptyQueryResultUpdateCallback(in SearchSessionId sessionId, in ISearchCallback callback);
+
void destroySearchSession(in SearchSessionId sessionId);
}
diff --git a/core/java/android/app/search/SearchSession.java b/core/java/android/app/search/SearchSession.java
index 10db337..eda68dd 100644
--- a/core/java/android/app/search/SearchSession.java
+++ b/core/java/android/app/search/SearchSession.java
@@ -28,8 +28,11 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
import dalvik.system.CloseGuard;
import java.util.List;
@@ -83,6 +86,8 @@
private final SearchSessionId mSessionId;
private final IBinder mToken = new Binder();
+ @GuardedBy("itself")
+ private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>();
/**
* Creates a new search ui client.
@@ -156,6 +161,95 @@
e.rethrowFromSystemServer();
}
}
+ /**
+ * Request the search ui service provide continuous updates of {@link SearchTarget} list
+ * via the provided callback to render for zero state, until the given callback is
+ * unregistered. Zero state means when user entered search ui but not issued any query yet.
+ *
+ * @see SearchSession.Callback#onTargetsAvailable(List).
+ *
+ * @param callbackExecutor The callback executor to use when calling the callback.
+ * @param callback The Callback to be called when updates of search targets for zero state
+ * are available.
+ */
+ public void registerEmptyQueryResultUpdateCallback(
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull Callback callback) {
+ synchronized (mRegisteredCallbacks) {
+ if (mIsClosed.get()) {
+ throw new IllegalStateException("This client has already been destroyed.");
+ }
+ if (mRegisteredCallbacks.containsKey(callback)) {
+ // Skip if this callback is already registered
+ return;
+ }
+ try {
+ final CallbackWrapper callbackWrapper = new CallbackWrapper(callbackExecutor,
+ callback::onTargetsAvailable);
+ mInterface.registerEmptyQueryResultUpdateCallback(mSessionId, callbackWrapper);
+ mRegisteredCallbacks.put(callback, callbackWrapper);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register for empty query result updates", e);
+ e.rethrowAsRuntimeException();
+ }
+ }
+ }
+
+ /**
+ * Requests the search ui service to stop providing continuous updates of {@link SearchTarget}
+ * to the provided callback for zero state until the callback is re-registered. Zero state
+ * means when user entered search ui but not issued any query yet.
+ *
+ * @see {@link SearchSession#registerEmptyQueryResultUpdateCallback(Executor, Callback)}
+ * @param callback The callback to be unregistered.
+ */
+ public void unregisterEmptyQueryResultUpdateCallback(
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull Callback callback) {
+ synchronized (mRegisteredCallbacks) {
+ if (mIsClosed.get()) {
+ throw new IllegalStateException("This client has already been destroyed.");
+ }
+
+ if (!mRegisteredCallbacks.containsKey(callback)) {
+ // Skip if this callback was never registered
+ return;
+ }
+ try {
+ final CallbackWrapper callbackWrapper = mRegisteredCallbacks.remove(callback);
+ mInterface.unregisterEmptyQueryResultUpdateCallback(mSessionId, callbackWrapper);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to unregister for empty query result updates", e);
+ e.rethrowAsRuntimeException();
+ }
+ }
+ }
+
+ /**
+ * Requests the search ui service to dispatch a new set of search targets to the pre-registered
+ * callback for zero state. Zero state means when user entered search ui but not issued any
+ * query yet. This method can be used for client to sync up with server data if they think data
+ * might be out of sync, for example, after restart.
+ * Pre-register a callback with
+ * {@link SearchSession#registerEmptyQueryResultUpdateCallback(Executor, Callback)}
+ * is required before calling this method. Otherwise this is no-op.
+ *
+ * @see {@link SearchSession#registerEmptyQueryResultUpdateCallback(Executor, Callback)}
+ * @see {@link SearchSession.Callback#onTargetsAvailable(List)}.
+ */
+ public void requestEmptyQueryResultUpdate() {
+ if (mIsClosed.get()) {
+ throw new IllegalStateException("This client has already been destroyed.");
+ }
+
+ try {
+ mInterface.requestEmptyQueryResultUpdate(mSessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to request empty query result update", e);
+ e.rethrowAsRuntimeException();
+ }
+ }
+
/**
* Destroys the client and unregisters the callback. Any method on this class after this call
@@ -213,6 +307,19 @@
}
}
+ /**
+ * Callback for receiving {@link SearchTarget} updates for zero state. Zero state
+ * means when user entered search ui but not issued any query yet.
+ */
+ public interface Callback {
+
+ /**
+ * Called when a new set of {@link SearchTarget} are available for zero state.
+ * @param targets Sorted list of search targets.
+ */
+ void onTargetsAvailable(@NonNull List<SearchTarget> targets);
+ }
+
static class CallbackWrapper extends Stub {
private final Consumer<List<SearchTarget>> mCallback;
diff --git a/core/java/android/app/time/TimeZoneCapabilities.java b/core/java/android/app/time/TimeZoneCapabilities.java
index b647fc3..4dee159 100644
--- a/core/java/android/app/time/TimeZoneCapabilities.java
+++ b/core/java/android/app/time/TimeZoneCapabilities.java
@@ -57,6 +57,16 @@
*/
@NonNull private final UserHandle mUserHandle;
private final @CapabilityState int mConfigureAutoDetectionEnabledCapability;
+
+ /**
+ * The values of the user's "Use location" value, AKA the Master Location Switch.
+ *
+ * <p>This is only exposed for SettingsUI and so is not part of the SDK API.
+ *
+ * <p>This is not treated as a CapabilityState as it's a boolean value that all user's have.
+ */
+ private final boolean mUseLocationEnabled;
+
private final @CapabilityState int mConfigureGeoDetectionEnabledCapability;
private final @CapabilityState int mSetManualTimeZoneCapability;
@@ -64,6 +74,7 @@
this.mUserHandle = Objects.requireNonNull(builder.mUserHandle);
this.mConfigureAutoDetectionEnabledCapability =
builder.mConfigureAutoDetectionEnabledCapability;
+ this.mUseLocationEnabled = builder.mUseLocationEnabled;
this.mConfigureGeoDetectionEnabledCapability =
builder.mConfigureGeoDetectionEnabledCapability;
this.mSetManualTimeZoneCapability = builder.mSetManualTimeZoneCapability;
@@ -74,6 +85,7 @@
UserHandle userHandle = UserHandle.readFromParcel(in);
return new TimeZoneCapabilities.Builder(userHandle)
.setConfigureAutoDetectionEnabledCapability(in.readInt())
+ .setUseLocationEnabled(in.readBoolean())
.setConfigureGeoDetectionEnabledCapability(in.readInt())
.setSetManualTimeZoneCapability(in.readInt())
.build();
@@ -83,6 +95,7 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
UserHandle.writeToParcel(mUserHandle, dest);
dest.writeInt(mConfigureAutoDetectionEnabledCapability);
+ dest.writeBoolean(mUseLocationEnabled);
dest.writeInt(mConfigureGeoDetectionEnabledCapability);
dest.writeInt(mSetManualTimeZoneCapability);
}
@@ -98,6 +111,20 @@
}
/**
+ * Returns {@code true} if the device's location can be used by the Android system, and
+ * therefore the platform components running on behalf of the user. At the time of writing, the
+ * user can change this via the "Use location" setting on the Location settings screen.
+ *
+ * Not part of the SDK API because it is intended for use by SettingsUI, which can display
+ * text about needing it to be on for location-based time zone detection.
+ * @hide
+ *
+ */
+ public boolean isUseLocationEnabled() {
+ return mUseLocationEnabled;
+ }
+
+ /**
* Returns the capability state associated with the user's ability to modify the geolocation
* detection setting. The setting can be updated via {@link
* TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)}.
@@ -167,6 +194,7 @@
return mUserHandle.equals(that.mUserHandle)
&& mConfigureAutoDetectionEnabledCapability
== that.mConfigureAutoDetectionEnabledCapability
+ && mUseLocationEnabled == that.mUseLocationEnabled
&& mConfigureGeoDetectionEnabledCapability
== that.mConfigureGeoDetectionEnabledCapability
&& mSetManualTimeZoneCapability == that.mSetManualTimeZoneCapability;
@@ -184,6 +212,7 @@
+ "mUserHandle=" + mUserHandle
+ ", mConfigureAutoDetectionEnabledCapability="
+ mConfigureAutoDetectionEnabledCapability
+ + ", mUseLocationEnabled=" + mUseLocationEnabled
+ ", mConfigureGeoDetectionEnabledCapability="
+ mConfigureGeoDetectionEnabledCapability
+ ", mSetManualTimeZoneCapability=" + mSetManualTimeZoneCapability
@@ -199,6 +228,7 @@
@NonNull private UserHandle mUserHandle;
private @CapabilityState int mConfigureAutoDetectionEnabledCapability;
+ private Boolean mUseLocationEnabled;
private @CapabilityState int mConfigureGeoDetectionEnabledCapability;
private @CapabilityState int mSetManualTimeZoneCapability;
@@ -211,6 +241,7 @@
mUserHandle = capabilitiesToCopy.mUserHandle;
mConfigureAutoDetectionEnabledCapability =
capabilitiesToCopy.mConfigureAutoDetectionEnabledCapability;
+ mUseLocationEnabled = capabilitiesToCopy.mUseLocationEnabled;
mConfigureGeoDetectionEnabledCapability =
capabilitiesToCopy.mConfigureGeoDetectionEnabledCapability;
mSetManualTimeZoneCapability =
@@ -223,6 +254,12 @@
return this;
}
+ /** Sets the values for "use location". See {@link #isUseLocationEnabled()}. */
+ public Builder setUseLocationEnabled(boolean useLocation) {
+ mUseLocationEnabled = useLocation;
+ return this;
+ }
+
/**
* Sets the value for the "configure geolocation time zone detection enabled" capability.
*/
@@ -242,6 +279,7 @@
public TimeZoneCapabilities build() {
verifyCapabilitySet(mConfigureAutoDetectionEnabledCapability,
"configureAutoDetectionEnabledCapability");
+ Objects.requireNonNull(mUseLocationEnabled, "useLocationEnabled");
verifyCapabilitySet(mConfigureGeoDetectionEnabledCapability,
"configureGeoDetectionEnabledCapability");
verifyCapabilitySet(mSetManualTimeZoneCapability,
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index 64dcc4d..3d76b28 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -17,6 +17,7 @@
package android.content;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.ActivityManager;
import android.app.ActivityThread;
@@ -26,6 +27,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.Process;
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
@@ -101,19 +103,22 @@
@UnsupportedAppUsage
boolean mFinished;
String mReceiverClassName;
+ final int mSentFromUid;
+ final String mSentFromPackage;
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public PendingResult(int resultCode, String resultData, Bundle resultExtras, int type,
boolean ordered, boolean sticky, IBinder token, int userId, int flags) {
this(resultCode, resultData, resultExtras, type, ordered, sticky,
- guessAssumeDelivered(type, ordered), token, userId, flags);
+ guessAssumeDelivered(type, ordered), token, userId, flags,
+ Process.INVALID_UID, null);
}
/** @hide */
public PendingResult(int resultCode, String resultData, Bundle resultExtras, int type,
boolean ordered, boolean sticky, boolean assumeDelivered, IBinder token,
- int userId, int flags) {
+ int userId, int flags, int sentFromUid, String sentFromPackage) {
mResultCode = resultCode;
mResultData = resultData;
mResultExtras = resultExtras;
@@ -124,6 +129,8 @@
mToken = token;
mSendingUser = userId;
mFlags = flags;
+ mSentFromUid = sentFromUid;
+ mSentFromPackage = sentFromPackage;
}
/** @hide */
@@ -322,6 +329,16 @@
return mSendingUser;
}
+ /** @hide */
+ public int getSentFromUid() {
+ return mSentFromUid;
+ }
+
+ /** @hide */
+ public String getSentFromPackage() {
+ return mSentFromPackage;
+ }
+
void checkSynchronousHint() {
// Note that we don't assert when receiving the initial sticky value,
// since that may have come from an ordered broadcast. We'll catch
@@ -687,6 +704,26 @@
}
/**
+ * Returns the uid of the app that initially sent this broadcast.
+ *
+ * @return the uid of the broadcasting app or {@link Process#INVALID_UID} if the current
+ * receiver cannot access the identity of the broadcasting app
+ */
+ public int getSentFromUid() {
+ return mPendingResult != null ? mPendingResult.mSentFromUid : Process.INVALID_UID;
+ }
+
+ /**
+ * Returns the package name of the app that initially sent this broadcast.
+ *
+ * @return the package name of the broadcasting app or {@code null} if the current
+ * receiver cannot access the identity of the broadcasting app
+ */
+ public @Nullable String getSentFromPackage() {
+ return mPendingResult != null ? mPendingResult.mSentFromPackage : null;
+ }
+
+ /**
* Control inclusion of debugging help for mismatched
* calls to {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
* Context.registerReceiver()}.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 5f1502f..a6e074c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2445,13 +2445,12 @@
* @see #sendBroadcast(Intent)
* @see #sendOrderedBroadcast(Intent, String)
* @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
- * @hide
*/
- @SuppressWarnings("HiddenAbstractMethod")
- @SystemApi
- public abstract void sendBroadcast(Intent intent,
+ public void sendBroadcast(@NonNull Intent intent,
@Nullable String receiverPermission,
- @Nullable Bundle options);
+ @Nullable Bundle options) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
/**
* Like {@link #sendBroadcast(Intent, String)}, but also allows specification
@@ -2487,6 +2486,32 @@
@Nullable String receiverPermission);
/**
+ * Broadcast the given intent to all interested BroadcastReceivers, delivering
+ * them one at a time to allow more preferred receivers to consume the
+ * broadcast before it is delivered to less preferred receivers. This
+ * call is asynchronous; it returns immediately, and you will continue
+ * executing while the receivers are run.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param receiverPermission (optional) String naming a permissions that
+ * a receiver must hold in order to receive your broadcast.
+ * If null, no permission is required.
+ * @param options (optional) Additional sending options, generated from a
+ * {@link android.app.BroadcastOptions}.
+ * @see android.content.BroadcastReceiver
+ * @see #registerReceiver
+ * @see #sendBroadcast(Intent)
+ * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
+ */
+ public void sendOrderedBroadcast(@NonNull Intent intent, @Nullable String receiverPermission,
+ @Nullable Bundle options) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* Version of {@link #sendBroadcast(Intent)} that allows you to
* receive data back from the broadcast. This is accomplished by
* supplying your own BroadcastReceiver when calling, which will be
@@ -2572,14 +2597,13 @@
* @see android.content.BroadcastReceiver
* @see #registerReceiver
* @see android.app.Activity#RESULT_OK
- * @hide
*/
- @SuppressWarnings("HiddenAbstractMethod")
- @SystemApi
- public abstract void sendOrderedBroadcast(@NonNull Intent intent,
+ public void sendOrderedBroadcast(@NonNull Intent intent,
@Nullable String receiverPermission, @Nullable Bundle options,
@Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler,
- int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras);
+ int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
/**
* Like {@link #sendOrderedBroadcast(Intent, String, BroadcastReceiver, android.os.Handler,
@@ -6151,10 +6175,10 @@
/**
* Use with {@link #getSystemService(String)} to retrieve a
- * {@link android.healthconnect.HealthConnectManager}.
+ * {@link android.health.connect.HealthConnectManager}.
*
* @see #getSystemService(String)
- * @see android.healthconnect.HealthConnectManager
+ * @see android.health.connect.HealthConnectManager
*/
public static final String HEALTHCONNECT_SERVICE = "healthconnect";
@@ -6198,6 +6222,16 @@
public static final String GRAMMATICAL_INFLECTION_SERVICE = "grammatical_inflection";
/**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.telephony.satellite.SatelliteManager} for accessing satellite functionality.
+ *
+ * @see #getSystemService(String)
+ * @see android.telephony.satellite.SatelliteManager
+ * @hide
+ */
+ public static final String SATELLITE_SERVICE = "satellite";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 9027e2e..a103128 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UiContext;
@@ -537,10 +538,8 @@
mBase.sendBroadcastAsUserMultiplePermissions(intent, user, receiverPermissions);
}
- /** @hide */
- @SystemApi
@Override
- public void sendBroadcast(Intent intent, @Nullable String receiverPermission,
+ public void sendBroadcast(@NonNull Intent intent, @Nullable String receiverPermission,
@Nullable Bundle options) {
mBase.sendBroadcast(intent, receiverPermission, options);
}
@@ -557,6 +556,14 @@
mBase.sendOrderedBroadcast(intent, receiverPermission);
}
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @Override
+ public void sendOrderedBroadcast(@NonNull Intent intent,
+ @Nullable String receiverPermission,
+ @Nullable Bundle options) {
+ mBase.sendOrderedBroadcast(intent, receiverPermission, options);
+ }
+
@Override
public void sendOrderedBroadcast(
Intent intent, @Nullable String receiverPermission,
@@ -567,11 +574,9 @@
initialData, initialExtras);
}
- /** @hide */
- @SystemApi
@Override
public void sendOrderedBroadcast(
- Intent intent, @Nullable String receiverPermission, @Nullable Bundle options,
+ @NonNull Intent intent, @Nullable String receiverPermission, @Nullable Bundle options,
@Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler,
int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) {
mBase.sendOrderedBroadcast(intent, receiverPermission,
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 5714032..d7ab6d7 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -31,8 +31,10 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.Activity;
import android.app.ActivityThread;
import android.app.AppGlobals;
+import android.app.StatusBarManager;
import android.bluetooth.BluetoothDevice;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
@@ -5137,6 +5139,86 @@
public static final String EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE";
/**
+ * Activity Action: Use with startActivityForResult to start a system activity that captures
+ * content on the screen to take a screenshot and present it to the user for editing. The
+ * edited screenshot is saved on device and returned to the calling activity as a {@link Uri}
+ * through {@link #getData()}. User interaction is required to return the edited screenshot to
+ * the calling activity.
+ *
+ * <p>This intent action requires the permission
+ * {@link android.Manifest.permission#LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE}.
+ *
+ * <p>Callers should query
+ * {@link StatusBarManager#canLaunchCaptureContentActivityForNote(Activity)} before showing a UI
+ * element that allows users to trigger this flow.
+ */
+ @RequiresPermission(Manifest.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE =
+ "android.intent.action.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE";
+
+ /**
+ * An int extra used by activity started with
+ * {@link #ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE} to indicate status of the response.
+ * This extra is used along with result code set to {@link android.app.Activity#RESULT_OK}.
+ *
+ * <p>The value for this extra can be one of the following:
+ * <ul>
+ * <li>{@link #CAPTURE_CONTENT_FOR_NOTE_SUCCESS}</li>
+ * <li>{@link #CAPTURE_CONTENT_FOR_NOTE_FAILED}</li>
+ * <li>{@link #CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED}</li>
+ * <li>{@link #CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED}</li>
+ * <li>{@link #CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN}</li>
+ * </ul>
+ */
+ public static final String EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE =
+ "android.intent.extra.CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE";
+
+ /**
+ * A response code used with {@link #EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE} to indicate
+ * that the request was a success.
+ *
+ * <p>This code will only be returned after the user has interacted with the system screenshot
+ * activity to consent to sharing the data with the note.
+ *
+ * <p>The captured screenshot is returned as a {@link Uri} through {@link #getData()}.
+ */
+ public static final int CAPTURE_CONTENT_FOR_NOTE_SUCCESS = 0;
+
+ /**
+ * A response code used with {@link #EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE} to indicate
+ * that something went wrong.
+ */
+ public static final int CAPTURE_CONTENT_FOR_NOTE_FAILED = 1;
+
+ /**
+ * A response code used with {@link #EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE} to indicate
+ * that user canceled the content capture flow.
+ */
+ public static final int CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED = 2;
+
+ /**
+ * A response code used with {@link #EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE} to indicate
+ * that the intent action {@link #ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE} was started
+ * by an activity that is running in a non-supported window mode.
+ */
+ public static final int CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED = 3;
+
+ /**
+ * A response code used with {@link #EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE} to indicate
+ * that screenshot is blocked by IT admin.
+ */
+ public static final int CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN = 4;
+
+ /** @hide */
+ @IntDef(value = {
+ CAPTURE_CONTENT_FOR_NOTE_SUCCESS, CAPTURE_CONTENT_FOR_NOTE_FAILED,
+ CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED,
+ CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CaptureContentForNoteStatusCodes {}
+
+ /**
* Broadcast Action: Sent to the integrity component when a package
* needs to be verified. The data contains the package URI along with other relevant
* information.
@@ -5829,7 +5911,6 @@
* in the sharesheet.
* A reselection action allows the user to return to the source app to change the content being
* shared.
- * @hide
*/
public static final String EXTRA_CHOOSER_PAYLOAD_RESELECTION_ACTION =
"android.intent.extra.CHOOSER_PAYLOAD_RESELECTION_ACTION";
diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java
index 8853b70..ccb53cf 100644
--- a/core/java/android/content/IntentSender.java
+++ b/core/java/android/content/IntentSender.java
@@ -18,9 +18,10 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityManager.PendingIntentInfo;
+import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.app.IApplicationThread;
-import android.app.ActivityManager.PendingIntentInfo;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Bundle;
import android.os.Handler;
@@ -160,7 +161,7 @@
*/
public void sendIntent(Context context, int code, Intent intent,
OnFinished onFinished, Handler handler) throws SendIntentException {
- sendIntent(context, code, intent, onFinished, handler, null);
+ sendIntent(context, code, intent, onFinished, handler, null, null /* options */);
}
/**
@@ -192,6 +193,42 @@
public void sendIntent(Context context, int code, Intent intent,
OnFinished onFinished, Handler handler, String requiredPermission)
throws SendIntentException {
+ sendIntent(context, code, intent, onFinished, handler, requiredPermission,
+ null /* options */);
+ }
+
+ /**
+ * Perform the operation associated with this IntentSender, allowing the
+ * caller to specify information about the Intent to use and be notified
+ * when the send has completed.
+ *
+ * @param context The Context of the caller. This may be null if
+ * <var>intent</var> is also null.
+ * @param code Result code to supply back to the IntentSender's target.
+ * @param intent Additional Intent data. See {@link Intent#fillIn
+ * Intent.fillIn()} for information on how this is applied to the
+ * original Intent. Use null to not modify the original Intent.
+ * @param onFinished The object to call back on when the send has
+ * completed, or null for no callback.
+ * @param handler Handler identifying the thread on which the callback
+ * should happen. If null, the callback will happen from the thread
+ * pool of the process.
+ * @param requiredPermission Name of permission that a recipient of the PendingIntent
+ * is required to hold. This is only valid for broadcast intents, and
+ * corresponds to the permission argument in
+ * {@link Context#sendBroadcast(Intent, String) Context.sendOrderedBroadcast(Intent, String)}.
+ * If null, no permission is required.
+ * @param options Additional options the caller would like to provide to modify the sending
+ * behavior. May be built from an {@link ActivityOptions} to apply to an activity start.
+ *
+ * @throws SendIntentException Throws CanceledIntentException if the IntentSender
+ * is no longer allowing more intents to be sent through it.
+ * @hide
+ */
+ public void sendIntent(Context context, int code, Intent intent,
+ OnFinished onFinished, Handler handler, String requiredPermission,
+ @Nullable Bundle options)
+ throws SendIntentException {
try {
String resolvedType = intent != null ?
intent.resolveTypeIfNeeded(context.getContentResolver())
@@ -203,7 +240,7 @@
onFinished != null
? new FinishedDispatcher(this, onFinished, handler)
: null,
- requiredPermission, null);
+ requiredPermission, options);
if (res < 0) {
throw new SendIntentException();
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 68b0631..fd73719 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1138,6 +1138,34 @@
264301586L; // buganizer id
/**
+ * This change id forces the packages it is applied to sandbox {@link android.view.View} API to
+ * an activity bounds for:
+ *
+ * <p>{@link android.view.View#getLocationOnScreen},
+ * {@link android.view.View#getWindowVisibleDisplayFrame},
+ * {@link android.view.View}#getWindowDisplayFrame,
+ * {@link android.view.View}#getBoundsOnScreen.
+ *
+ * <p>For {@link android.view.View#getWindowVisibleDisplayFrame} and
+ * {@link android.view.View}#getWindowDisplayFrame this sandboxing is happening indirectly
+ * through
+ * {@link android.view.ViewRootImpl}#getWindowVisibleDisplayFrame,
+ * {@link android.view.ViewRootImpl}#getDisplayFrame respectively.
+ *
+ * <p>Some applications assume that they occupy the whole screen and therefore use the display
+ * coordinates in their calculations as if an activity is positioned in the top-left corner of
+ * the screen, with left coordinate equal to 0. This may not be the case of applications in
+ * multi-window and in letterbox modes. This can lead to shifted or out of bounds UI elements in
+ * case the activity is Letterboxed or is in multi-window mode.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // buganizer id
+
+ /**
* This change id is the gatekeeper for all treatments that force a given min aspect ratio.
* Enabling this change will allow the following min aspect ratio treatments to be applied:
* OVERRIDE_MIN_ASPECT_RATIO_MEDIUM
@@ -1285,6 +1313,18 @@
public static final long OVERRIDE_ANY_ORIENTATION = 265464455L;
/**
+ * When enabled, activates OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE,
+ * OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR and OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT
+ * only when an app is connected to the camera. See
+ * com.android.server.wm.DisplayRotationCompatPolicy for more context.
+ * @hide
+ */
+ @ChangeId
+ @Disabled
+ @Overridable
+ public static final long OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA = 265456536L;
+
+ /**
* This override fixes display orientation to landscape natural orientation when a task is
* fullscreen. While display rotation is fixed to landscape, the orientation requested by the
* activity will be still respected by bounds resolution logic. For instance, if an activity
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index e747a6f..45100be 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1060,7 +1060,7 @@
PackageInstaller.EXTRA_INSTALL_CONSTRAINTS_RESULT,
InstallConstraintsResult.class);
try {
- if (result.isAllConstraintsSatisfied()) {
+ if (result.areAllConstraintsSatisfied()) {
session.commit(statusReceiver, false);
} else {
// timeout
@@ -2042,7 +2042,7 @@
*/
@SystemApi
@NonNull
- public InstallInfo getInstallInfo(@NonNull File file, int flags)
+ public InstallInfo readInstallInfo(@NonNull File file, int flags)
throws PackageParsingException {
final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
@@ -2616,8 +2616,17 @@
installFlags |= PackageManager.INSTALL_FORCE_PERMISSION_PROMPT;
}
- /** {@hide} */
- @SystemApi
+ /**
+ * Requests that the system not kill any of the package's running
+ * processes as part of a {@link SessionParams#MODE_INHERIT_EXISTING}
+ * session in which splits being added. By default, all installs will
+ * result in the package's running processes being killed before the
+ * install completes.
+ *
+ * @param dontKillApp set to {@code true} to request that the processes
+ * belonging to the package not be killed as part of
+ * this install.
+ */
public void setDontKillApp(boolean dontKillApp) {
if (dontKillApp) {
installFlags |= PackageManager.INSTALL_DONT_KILL_APP;
@@ -3456,10 +3465,7 @@
/**
* Get the value set in {@link SessionParams#setDontKillApp(boolean)}.
- *
- * @hide
*/
- @SystemApi
public boolean getDontKillApp() {
return (installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0;
}
@@ -3740,7 +3746,7 @@
/**
* Returns whether this session has requested user pre-approval.
*/
- public @NonNull boolean getIsPreApprovalRequested() {
+ public boolean isPreApprovalRequested() {
return isPreapprovalRequested;
}
@@ -4110,12 +4116,20 @@
* The callback result of {@link #checkInstallConstraints(List, InstallConstraints, Executor, Consumer)}.
*/
@DataClass(genParcelable = true, genHiddenConstructor = true)
+ @DataClass.Suppress("isAllConstraintsSatisfied")
public static final class InstallConstraintsResult implements Parcelable {
/**
* True if all constraints are satisfied.
*/
private boolean mAllConstraintsSatisfied;
+ /**
+ * True if all constraints are satisfied.
+ */
+ public boolean areAllConstraintsSatisfied() {
+ return mAllConstraintsSatisfied;
+ }
+
// Code below generated by codegen v1.0.23.
@@ -4146,14 +4160,6 @@
// onConstructed(); // You can define this method to get a callback
}
- /**
- * True if all constraints are satisfied.
- */
- @DataClass.Generated.Member
- public boolean isAllConstraintsSatisfied() {
- return mAllConstraintsSatisfied;
- }
-
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -4199,10 +4205,10 @@
};
@DataClass.Generated(
- time = 1668650523745L,
+ time = 1675135664641L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
- inputSignatures = "private boolean mAllConstraintsSatisfied\nclass InstallConstraintsResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)")
+ inputSignatures = "private boolean mAllConstraintsSatisfied\npublic boolean areAllConstraintsSatisfied()\nclass InstallConstraintsResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)")
@Deprecated
private void __metadata() {}
@@ -4241,41 +4247,41 @@
*/
@NonNull
public static final InstallConstraints GENTLE_UPDATE =
- new Builder().requireAppNotInteracting().build();
+ new Builder().setAppNotInteractingRequired().build();
- private final boolean mRequireDeviceIdle;
- private final boolean mRequireAppNotForeground;
- private final boolean mRequireAppNotInteracting;
- private final boolean mRequireAppNotTopVisible;
- private final boolean mRequireNotInCall;
+ private final boolean mDeviceIdleRequired;
+ private final boolean mAppNotForegroundRequired;
+ private final boolean mAppNotInteractingRequired;
+ private final boolean mAppNotTopVisibleRequired;
+ private final boolean mNotInCallRequired;
/**
* Builder class for constructing {@link InstallConstraints}.
*/
public static final class Builder {
- private boolean mRequireDeviceIdle;
- private boolean mRequireAppNotForeground;
- private boolean mRequireAppNotInteracting;
- private boolean mRequireAppNotTopVisible;
- private boolean mRequireNotInCall;
+ private boolean mDeviceIdleRequired;
+ private boolean mAppNotForegroundRequired;
+ private boolean mAppNotInteractingRequired;
+ private boolean mAppNotTopVisibleRequired;
+ private boolean mNotInCallRequired;
/**
* This constraint requires the device is idle.
*/
- @SuppressLint("BuilderSetStyle")
+ @SuppressLint("MissingGetterMatchingBuilder")
@NonNull
- public Builder requireDeviceIdle() {
- mRequireDeviceIdle = true;
+ public Builder setDeviceIdleRequired() {
+ mDeviceIdleRequired = true;
return this;
}
/**
* This constraint requires the app in question is not in the foreground.
*/
- @SuppressLint("BuilderSetStyle")
+ @SuppressLint("MissingGetterMatchingBuilder")
@NonNull
- public Builder requireAppNotForeground() {
- mRequireAppNotForeground = true;
+ public Builder setAppNotForegroundRequired() {
+ mAppNotForegroundRequired = true;
return this;
}
@@ -4288,10 +4294,10 @@
* <li>being visible to the user</li>
* </ul>
*/
- @SuppressLint("BuilderSetStyle")
+ @SuppressLint("MissingGetterMatchingBuilder")
@NonNull
- public Builder requireAppNotInteracting() {
- mRequireAppNotInteracting = true;
+ public Builder setAppNotInteractingRequired() {
+ mAppNotInteractingRequired = true;
return this;
}
@@ -4300,25 +4306,27 @@
* A top-visible app is showing UI at the top of the screen that the user is
* interacting with.
*
- * Note this constraint is a subset of {@link #requireAppNotForeground()}
+ * Note this constraint is a subset of {@link #setAppNotForegroundRequired()}
* because a top-visible app is also a foreground app. This is also a subset
- * of {@link #requireAppNotInteracting()} because a top-visible app is interacting
+ * of {@link #setAppNotInteractingRequired()} because a top-visible app is interacting
* with the user.
+ *
+ * @see ActivityManager.RunningAppProcessInfo#IMPORTANCE_FOREGROUND
*/
- @SuppressLint("BuilderSetStyle")
+ @SuppressLint("MissingGetterMatchingBuilder")
@NonNull
- public Builder requireAppNotTopVisible() {
- mRequireAppNotTopVisible = true;
+ public Builder setAppNotTopVisibleRequired() {
+ mAppNotTopVisibleRequired = true;
return this;
}
/**
* This constraint requires there is no ongoing call in the device.
*/
- @SuppressLint("BuilderSetStyle")
+ @SuppressLint("MissingGetterMatchingBuilder")
@NonNull
- public Builder requireNotInCall() {
- mRequireNotInCall = true;
+ public Builder setNotInCallRequired() {
+ mNotInCallRequired = true;
return this;
}
@@ -4327,8 +4335,8 @@
*/
@NonNull
public InstallConstraints build() {
- return new InstallConstraints(mRequireDeviceIdle, mRequireAppNotForeground,
- mRequireAppNotInteracting, mRequireAppNotTopVisible, mRequireNotInCall);
+ return new InstallConstraints(mDeviceIdleRequired, mAppNotForegroundRequired,
+ mAppNotInteractingRequired, mAppNotTopVisibleRequired, mNotInCallRequired);
}
}
@@ -4354,43 +4362,43 @@
*/
@DataClass.Generated.Member
public InstallConstraints(
- boolean requireDeviceIdle,
- boolean requireAppNotForeground,
- boolean requireAppNotInteracting,
- boolean requireAppNotTopVisible,
- boolean requireNotInCall) {
- this.mRequireDeviceIdle = requireDeviceIdle;
- this.mRequireAppNotForeground = requireAppNotForeground;
- this.mRequireAppNotInteracting = requireAppNotInteracting;
- this.mRequireAppNotTopVisible = requireAppNotTopVisible;
- this.mRequireNotInCall = requireNotInCall;
+ boolean deviceIdleRequired,
+ boolean appNotForegroundRequired,
+ boolean appNotInteractingRequired,
+ boolean appNotTopVisibleRequired,
+ boolean notInCallRequired) {
+ this.mDeviceIdleRequired = deviceIdleRequired;
+ this.mAppNotForegroundRequired = appNotForegroundRequired;
+ this.mAppNotInteractingRequired = appNotInteractingRequired;
+ this.mAppNotTopVisibleRequired = appNotTopVisibleRequired;
+ this.mNotInCallRequired = notInCallRequired;
// onConstructed(); // You can define this method to get a callback
}
@DataClass.Generated.Member
- public boolean isRequireDeviceIdle() {
- return mRequireDeviceIdle;
+ public boolean isDeviceIdleRequired() {
+ return mDeviceIdleRequired;
}
@DataClass.Generated.Member
- public boolean isRequireAppNotForeground() {
- return mRequireAppNotForeground;
+ public boolean isAppNotForegroundRequired() {
+ return mAppNotForegroundRequired;
}
@DataClass.Generated.Member
- public boolean isRequireAppNotInteracting() {
- return mRequireAppNotInteracting;
+ public boolean isAppNotInteractingRequired() {
+ return mAppNotInteractingRequired;
}
@DataClass.Generated.Member
- public boolean isRequireAppNotTopVisible() {
- return mRequireAppNotTopVisible;
+ public boolean isAppNotTopVisibleRequired() {
+ return mAppNotTopVisibleRequired;
}
@DataClass.Generated.Member
- public boolean isRequireNotInCall() {
- return mRequireNotInCall;
+ public boolean isNotInCallRequired() {
+ return mNotInCallRequired;
}
@Override
@@ -4406,11 +4414,11 @@
InstallConstraints that = (InstallConstraints) o;
//noinspection PointlessBooleanExpression
return true
- && mRequireDeviceIdle == that.mRequireDeviceIdle
- && mRequireAppNotForeground == that.mRequireAppNotForeground
- && mRequireAppNotInteracting == that.mRequireAppNotInteracting
- && mRequireAppNotTopVisible == that.mRequireAppNotTopVisible
- && mRequireNotInCall == that.mRequireNotInCall;
+ && mDeviceIdleRequired == that.mDeviceIdleRequired
+ && mAppNotForegroundRequired == that.mAppNotForegroundRequired
+ && mAppNotInteractingRequired == that.mAppNotInteractingRequired
+ && mAppNotTopVisibleRequired == that.mAppNotTopVisibleRequired
+ && mNotInCallRequired == that.mNotInCallRequired;
}
@Override
@@ -4420,11 +4428,11 @@
// int fieldNameHashCode() { ... }
int _hash = 1;
- _hash = 31 * _hash + Boolean.hashCode(mRequireDeviceIdle);
- _hash = 31 * _hash + Boolean.hashCode(mRequireAppNotForeground);
- _hash = 31 * _hash + Boolean.hashCode(mRequireAppNotInteracting);
- _hash = 31 * _hash + Boolean.hashCode(mRequireAppNotTopVisible);
- _hash = 31 * _hash + Boolean.hashCode(mRequireNotInCall);
+ _hash = 31 * _hash + Boolean.hashCode(mDeviceIdleRequired);
+ _hash = 31 * _hash + Boolean.hashCode(mAppNotForegroundRequired);
+ _hash = 31 * _hash + Boolean.hashCode(mAppNotInteractingRequired);
+ _hash = 31 * _hash + Boolean.hashCode(mAppNotTopVisibleRequired);
+ _hash = 31 * _hash + Boolean.hashCode(mNotInCallRequired);
return _hash;
}
@@ -4435,11 +4443,11 @@
// void parcelFieldName(Parcel dest, int flags) { ... }
byte flg = 0;
- if (mRequireDeviceIdle) flg |= 0x1;
- if (mRequireAppNotForeground) flg |= 0x2;
- if (mRequireAppNotInteracting) flg |= 0x4;
- if (mRequireAppNotTopVisible) flg |= 0x8;
- if (mRequireNotInCall) flg |= 0x10;
+ if (mDeviceIdleRequired) flg |= 0x1;
+ if (mAppNotForegroundRequired) flg |= 0x2;
+ if (mAppNotInteractingRequired) flg |= 0x4;
+ if (mAppNotTopVisibleRequired) flg |= 0x8;
+ if (mNotInCallRequired) flg |= 0x10;
dest.writeByte(flg);
}
@@ -4455,17 +4463,17 @@
// static FieldType unparcelFieldName(Parcel in) { ... }
byte flg = in.readByte();
- boolean requireDeviceIdle = (flg & 0x1) != 0;
- boolean requireAppNotForeground = (flg & 0x2) != 0;
- boolean requireAppNotInteracting = (flg & 0x4) != 0;
- boolean requireAppNotTopVisible = (flg & 0x8) != 0;
- boolean requireNotInCall = (flg & 0x10) != 0;
+ boolean deviceIdleRequired = (flg & 0x1) != 0;
+ boolean appNotForegroundRequired = (flg & 0x2) != 0;
+ boolean appNotInteractingRequired = (flg & 0x4) != 0;
+ boolean appNotTopVisibleRequired = (flg & 0x8) != 0;
+ boolean notInCallRequired = (flg & 0x10) != 0;
- this.mRequireDeviceIdle = requireDeviceIdle;
- this.mRequireAppNotForeground = requireAppNotForeground;
- this.mRequireAppNotInteracting = requireAppNotInteracting;
- this.mRequireAppNotTopVisible = requireAppNotTopVisible;
- this.mRequireNotInCall = requireNotInCall;
+ this.mDeviceIdleRequired = deviceIdleRequired;
+ this.mAppNotForegroundRequired = appNotForegroundRequired;
+ this.mAppNotInteractingRequired = appNotInteractingRequired;
+ this.mAppNotTopVisibleRequired = appNotTopVisibleRequired;
+ this.mNotInCallRequired = notInCallRequired;
// onConstructed(); // You can define this method to get a callback
}
@@ -4485,10 +4493,10 @@
};
@DataClass.Generated(
- time = 1670207178734L,
+ time = 1675135664653L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
- inputSignatures = "public static final @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE\nprivate final boolean mRequireDeviceIdle\nprivate final boolean mRequireAppNotForeground\nprivate final boolean mRequireAppNotInteracting\nprivate final boolean mRequireAppNotTopVisible\nprivate final boolean mRequireNotInCall\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mRequireDeviceIdle\nprivate boolean mRequireAppNotForeground\nprivate boolean mRequireAppNotInteracting\nprivate boolean mRequireAppNotTopVisible\nprivate boolean mRequireNotInCall\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall()\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genEqualsHashCode=true)")
+ inputSignatures = "public static final @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE\nprivate final boolean mDeviceIdleRequired\nprivate final boolean mAppNotForegroundRequired\nprivate final boolean mAppNotInteractingRequired\nprivate final boolean mAppNotTopVisibleRequired\nprivate final boolean mNotInCallRequired\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mDeviceIdleRequired\nprivate boolean mAppNotForegroundRequired\nprivate boolean mAppNotInteractingRequired\nprivate boolean mAppNotTopVisibleRequired\nprivate boolean mNotInCallRequired\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setDeviceIdleRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotForegroundRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotInteractingRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotTopVisibleRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setNotInCallRequired()\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index f3209f9..49d21da 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -401,8 +401,9 @@
*
* <p>If the service isn't stopped within the timeout,
* {@link android.app.Service#onTimeout(int)} will be called.
- * If the service is still not stopped after the callback,
- * the app will be declared an ANR.
+ *
+ * <p>If the service is still not stopped after the callback,
+ * the app will be declared an ANR after a short grace period of several seconds.
*
* <li>
* A foreground service of this type cannot be made "sticky"
@@ -419,6 +420,17 @@
* </a>
* </ul>
*
+ * <p>Note, even though
+ * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}
+ * was added
+ * on Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * it can be also used on
+ * on prior android versions (just like other new foreground service types can be used).
+ * However, because {@link android.app.Service#onTimeout(int)} did not exist on prior versions,
+ * it will never called on such versions.
+ * Because of this, developers must make sure to stop the foreground service even if
+ * {@link android.app.Service#onTimeout(int)} is not called on such versions.
+ *
* @see android.app.Service#onTimeout(int)
*/
public static final int FOREGROUND_SERVICE_TYPE_SHORT_SERVICE = 1 << 11;
diff --git a/core/java/android/credentials/GetCredentialOption.java b/core/java/android/credentials/CredentialOption.java
similarity index 79%
rename from core/java/android/credentials/GetCredentialOption.java
rename to core/java/android/credentials/CredentialOption.java
index f2895c7..9a3b46d 100644
--- a/core/java/android/credentials/GetCredentialOption.java
+++ b/core/java/android/credentials/CredentialOption.java
@@ -27,9 +27,11 @@
import com.android.internal.util.Preconditions;
/**
- * A specific type of credential request.
+ * Information about a specific type of credential to be requested during a {@link
+ * CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor,
+ * OutcomeReceiver)} operation.
*/
-public final class GetCredentialOption implements Parcelable {
+public final class CredentialOption implements Parcelable {
/**
* Bundle key to the flattened version of the JSON request string. Framework will use this key
@@ -118,7 +120,7 @@
@Override
public String toString() {
- return "GetCredentialOption {"
+ return "CredentialOption {"
+ "type=" + mType
+ ", requestData=" + mCredentialRetrievalData
+ ", candidateQueryData=" + mCandidateQueryData
@@ -127,17 +129,17 @@
}
/**
- * Constructs a {@link GetCredentialOption}.
+ * Constructs a {@link CredentialOption}.
*
- * @param type the requested credential type
- * @param credentialRetrievalData the request data
- * @param candidateQueryData the partial request data that will be sent to the provider
- * during the initial credential candidate query stage
- * @param isSystemProviderRequired whether the request must only be fulfilled by a system
- * provider
+ * @param type the requested credential type
+ * @param credentialRetrievalData the request data
+ * @param candidateQueryData the partial request data that will be sent to the provider
+ * during the initial credential candidate query stage
+ * @param isSystemProviderRequired whether the request must only be fulfilled by a system
+ * provider
* @throws IllegalArgumentException If type is empty.
*/
- public GetCredentialOption(
+ public CredentialOption(
@NonNull String type,
@NonNull Bundle credentialRetrievalData,
@NonNull Bundle candidateQueryData,
@@ -150,7 +152,7 @@
mIsSystemProviderRequired = isSystemProviderRequired;
}
- private GetCredentialOption(@NonNull Parcel in) {
+ private CredentialOption(@NonNull Parcel in) {
String type = in.readString8();
Bundle data = in.readBundle();
Bundle candidateQueryData = in.readBundle();
@@ -165,16 +167,16 @@
mIsSystemProviderRequired = isSystemProviderRequired;
}
- public static final @NonNull Parcelable.Creator<GetCredentialOption> CREATOR =
- new Parcelable.Creator<GetCredentialOption>() {
- @Override
- public GetCredentialOption[] newArray(int size) {
- return new GetCredentialOption[size];
- }
+ @NonNull
+ public static final Parcelable.Creator<CredentialOption> CREATOR = new Parcelable.Creator<>() {
+ @Override
+ public CredentialOption[] newArray(int size) {
+ return new CredentialOption[size];
+ }
- @Override
- public GetCredentialOption createFromParcel(@NonNull Parcel in) {
- return new GetCredentialOption(in);
- }
- };
+ @Override
+ public CredentialOption createFromParcel(@NonNull Parcel in) {
+ return new CredentialOption(in);
+ }
+ };
}
diff --git a/core/java/android/credentials/GetCredentialRequest.java b/core/java/android/credentials/GetCredentialRequest.java
index 85b4468..a869c5b 100644
--- a/core/java/android/credentials/GetCredentialRequest.java
+++ b/core/java/android/credentials/GetCredentialRequest.java
@@ -39,7 +39,7 @@
* The list of credential requests.
*/
@NonNull
- private final List<GetCredentialOption> mGetCredentialOptions;
+ private final List<CredentialOption> mCredentialOptions;
/**
* The top request level data.
@@ -51,8 +51,8 @@
* Returns the list of credential options to be requested.
*/
@NonNull
- public List<GetCredentialOption> getGetCredentialOptions() {
- return mGetCredentialOptions;
+ public List<CredentialOption> getCredentialOptions() {
+ return mCredentialOptions;
}
/**
@@ -65,7 +65,7 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeTypedList(mGetCredentialOptions, flags);
+ dest.writeTypedList(mCredentialOptions, flags);
dest.writeBundle(mData);
}
@@ -76,29 +76,29 @@
@Override
public String toString() {
- return "GetCredentialRequest {getCredentialOption=" + mGetCredentialOptions
+ return "GetCredentialRequest {credentialOption=" + mCredentialOptions
+ ", data=" + mData
+ "}";
}
- private GetCredentialRequest(@NonNull List<GetCredentialOption> getCredentialOptions,
+ private GetCredentialRequest(@NonNull List<CredentialOption> credentialOptions,
@NonNull Bundle data) {
Preconditions.checkCollectionNotEmpty(
- getCredentialOptions,
- /*valueName=*/ "getCredentialOptions");
+ credentialOptions,
+ /*valueName=*/ "credentialOptions");
Preconditions.checkCollectionElementsNotNull(
- getCredentialOptions,
- /*valueName=*/ "getCredentialOptions");
- mGetCredentialOptions = getCredentialOptions;
+ credentialOptions,
+ /*valueName=*/ "credentialOptions");
+ mCredentialOptions = credentialOptions;
mData = requireNonNull(data,
"data must not be null");
}
private GetCredentialRequest(@NonNull Parcel in) {
- List<GetCredentialOption> getCredentialOptions = new ArrayList<GetCredentialOption>();
- in.readTypedList(getCredentialOptions, GetCredentialOption.CREATOR);
- mGetCredentialOptions = getCredentialOptions;
- AnnotationValidations.validate(NonNull.class, null, mGetCredentialOptions);
+ List<CredentialOption> credentialOptions = new ArrayList<CredentialOption>();
+ in.readTypedList(credentialOptions, CredentialOption.CREATOR);
+ mCredentialOptions = credentialOptions;
+ AnnotationValidations.validate(NonNull.class, null, mCredentialOptions);
Bundle data = in.readBundle();
@@ -106,8 +106,8 @@
AnnotationValidations.validate(NonNull.class, null, mData);
}
- public static final @NonNull Parcelable.Creator<GetCredentialRequest> CREATOR =
- new Parcelable.Creator<GetCredentialRequest>() {
+ @NonNull public static final Parcelable.Creator<GetCredentialRequest> CREATOR =
+ new Parcelable.Creator<>() {
@Override
public GetCredentialRequest[] newArray(int size) {
return new GetCredentialRequest[size];
@@ -123,7 +123,7 @@
public static final class Builder {
@NonNull
- private List<GetCredentialOption> mGetCredentialOptions = new ArrayList<>();
+ private List<CredentialOption> mCredentialOptions = new ArrayList<>();
@NonNull
private final Bundle mData;
@@ -136,43 +136,42 @@
}
/**
- * Adds a specific type of {@link GetCredentialOption}.
+ * Adds a specific type of {@link CredentialOption}.
*/
@NonNull
- public Builder addGetCredentialOption(
- @NonNull GetCredentialOption getCredentialOption) {
- mGetCredentialOptions.add(requireNonNull(
- getCredentialOption, "getCredentialOption must not be null"));
+ public Builder addCredentialOption(@NonNull CredentialOption credentialOption) {
+ mCredentialOptions.add(requireNonNull(
+ credentialOption, "credentialOption must not be null"));
return this;
}
/**
- * Sets the list of {@link GetCredentialOption}.
+ * Sets the list of {@link CredentialOption}.
*/
@NonNull
- public Builder setGetCredentialOptions(
- @NonNull List<GetCredentialOption> getCredentialOptions) {
+ public Builder setCredentialOptions(
+ @NonNull List<CredentialOption> credentialOptions) {
Preconditions.checkCollectionElementsNotNull(
- getCredentialOptions,
- /*valueName=*/ "getCredentialOptions");
- mGetCredentialOptions = new ArrayList<>(getCredentialOptions);
+ credentialOptions,
+ /*valueName=*/ "credentialOptions");
+ mCredentialOptions = new ArrayList<>(credentialOptions);
return this;
}
/**
* Builds a {@link GetCredentialRequest}.
*
- * @throws IllegalArgumentException If getCredentialOptions is empty.
+ * @throws IllegalArgumentException If credentialOptions is empty.
*/
@NonNull
public GetCredentialRequest build() {
Preconditions.checkCollectionNotEmpty(
- mGetCredentialOptions,
- /*valueName=*/ "getCredentialOptions");
+ mCredentialOptions,
+ /*valueName=*/ "credentialOptions");
Preconditions.checkCollectionElementsNotNull(
- mGetCredentialOptions,
- /*valueName=*/ "getCredentialOptions");
- return new GetCredentialRequest(mGetCredentialOptions, mData);
+ mCredentialOptions,
+ /*valueName=*/ "credentialOptions");
+ return new GetCredentialRequest(mCredentialOptions, mData);
}
}
}
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index da847a8..19719a8 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -2387,9 +2387,8 @@
}
//TODO: Do we need to treat this as error?
- if (!mDeviceStatus.containsKey(id) || !isAvailable(mDeviceStatus.get(id))
- || !mUnavailablePhysicalDevices.containsKey(id)) {
- Log.e(TAG, String.format("Camera %s is not available. Ignore physical camera "
+ if (!mDeviceStatus.containsKey(id) || !mUnavailablePhysicalDevices.containsKey(id)) {
+ Log.e(TAG, String.format("Camera %s is not present. Ignore physical camera "
+ "status change", id));
return;
}
@@ -2414,6 +2413,12 @@
return;
}
+ if (!isAvailable(mDeviceStatus.get(id))) {
+ Log.i(TAG, String.format("Camera %s is not available. Ignore physical camera "
+ + "status change callback(s)", id));
+ return;
+ }
+
final int callbackCount = mCallbackMap.size();
for (int i = 0; i < callbackCount; i++) {
Executor executor = mCallbackMap.valueAt(i);
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 08238ca..d49cc44 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -66,6 +66,7 @@
public final class DisplayManager {
private static final String TAG = "DisplayManager";
private static final boolean DEBUG = false;
+ private static final boolean ENABLE_VIRTUAL_DISPLAY_REFRESH_RATE = false;
private final Context mContext;
private final DisplayManagerGlobal mGlobal;
@@ -114,6 +115,23 @@
"android.hardware.display.category.PRESENTATION";
/**
+ * Display category: Rear displays.
+ * <p>
+ * This category can be used to identify complementary internal displays that are facing away
+ * from the user.
+ * Certain applications may present to this display.
+ * Similar to presentation displays.
+ * </p>
+ *
+ * @see android.app.Presentation
+ * @see Display#FLAG_PRESENTATION
+ * @see #getDisplays(String)
+ * @hide
+ */
+ public static final String DISPLAY_CATEGORY_REAR =
+ "android.hardware.display.category.REAR";
+
+ /**
* Display category: All displays, including disabled displays.
* <p>
* This returns all displays, including currently disabled and inaccessible displays.
@@ -618,11 +636,19 @@
synchronized (mLock) {
try {
if (DISPLAY_CATEGORY_PRESENTATION.equals(category)) {
- addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_WIFI);
- addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_EXTERNAL);
- addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_OVERLAY);
- addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_VIRTUAL);
- addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_INTERNAL);
+ addDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_WIFI,
+ Display.FLAG_PRESENTATION);
+ addDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_EXTERNAL,
+ Display.FLAG_PRESENTATION);
+ addDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_OVERLAY,
+ Display.FLAG_PRESENTATION);
+ addDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_VIRTUAL,
+ Display.FLAG_PRESENTATION);
+ addDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_INTERNAL,
+ Display.FLAG_PRESENTATION);
+ } else if (DISPLAY_CATEGORY_REAR.equals(category)) {
+ addDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_INTERNAL,
+ Display.FLAG_REAR);
} else if (category == null
|| DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED.equals(category)) {
addAllDisplaysLocked(mTempDisplays, displayIds);
@@ -643,15 +669,16 @@
}
}
- private void addPresentationDisplaysLocked(
- ArrayList<Display> displays, int[] displayIds, int matchType) {
- for (int i = 0; i < displayIds.length; i++) {
- if (displayIds[i] == DEFAULT_DISPLAY) {
+ private void addDisplaysLocked(
+ ArrayList<Display> displays, int[] displayIds, int matchType, int flagMask) {
+ for (int displayId : displayIds) {
+ if (displayId == DEFAULT_DISPLAY) {
continue;
}
- Display display = getOrCreateDisplayLocked(displayIds[i], true /*assumeValid*/);
+
+ Display display = getOrCreateDisplayLocked(displayId, /* assumeValid= */ true);
if (display != null
- && (display.getFlags() & Display.FLAG_PRESENTATION) != 0
+ && (display.getFlags() & flagMask) == flagMask
&& display.getType() == matchType) {
displays.add(display);
}
@@ -938,6 +965,24 @@
/**
* Creates a virtual display.
+ *
+ * @see #createVirtualDisplay(String, int, int, int, float, Surface, int,
+ * Handler, VirtualDisplay.Callback)
+ */
+ @Nullable
+ public VirtualDisplay createVirtualDisplay(@NonNull String name,
+ @IntRange(from = 1) int width,
+ @IntRange(from = 1) int height,
+ @IntRange(from = 1) int densityDpi,
+ float requestedRefreshRate,
+ @Nullable Surface surface,
+ @VirtualDisplayFlag int flags) {
+ return createVirtualDisplay(name, width, height, densityDpi, requestedRefreshRate,
+ surface, flags, null, null);
+ }
+
+ /**
+ * Creates a virtual display.
* <p>
* The content of a virtual display is rendered to a {@link Surface} provided
* by the application.
@@ -987,12 +1032,82 @@
@VirtualDisplayFlag int flags,
@Nullable VirtualDisplay.Callback callback,
@Nullable Handler handler) {
+ return createVirtualDisplay(name, width, height, densityDpi, 0.0f, surface,
+ flags, handler, callback);
+ }
+
+ /**
+ * Creates a virtual display.
+ * <p>
+ * The content of a virtual display is rendered to a {@link Surface} provided
+ * by the application.
+ * </p><p>
+ * The virtual display should be {@link VirtualDisplay#release released}
+ * when no longer needed. Because a virtual display renders to a surface
+ * provided by the application, it will be released automatically when the
+ * process terminates and all remaining windows on it will be forcibly removed.
+ * </p><p>
+ * The behavior of the virtual display depends on the flags that are provided
+ * to this method. By default, virtual displays are created to be private,
+ * non-presentation and unsecure. Permissions may be required to use certain flags.
+ * </p><p>
+ * As of {@link android.os.Build.VERSION_CODES#KITKAT_WATCH}, the surface may
+ * be attached or detached dynamically using {@link VirtualDisplay#setSurface}.
+ * Previously, the surface had to be non-null when {@link #createVirtualDisplay}
+ * was called and could not be changed for the lifetime of the display.
+ * </p><p>
+ * Detaching the surface that backs a virtual display has a similar effect to
+ * turning off the screen.
+ * </p>
+ *
+ * @param name The name of the virtual display, must be non-empty.
+ * @param width The width of the virtual display in pixels, must be greater than 0.
+ * @param height The height of the virtual display in pixels, must be greater than 0.
+ * @param densityDpi The density of the virtual display in dpi, must be greater than 0.
+ * @param requestedRefreshRate The requested refresh rate in frames per second.
+ * For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on
+ * 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded
+ * up or down to a divisor of the physical display. If 0 is specified, the virtual
+ * display is refreshed at the physical display refresh rate.
+ * @param surface The surface to which the content of the virtual display should
+ * be rendered, or null if there is none initially.
+ * @param flags A combination of virtual display flags:
+ * {@link #VIRTUAL_DISPLAY_FLAG_PUBLIC}, {@link #VIRTUAL_DISPLAY_FLAG_PRESENTATION},
+ * {@link #VIRTUAL_DISPLAY_FLAG_SECURE}, {@link #VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY},
+ * or {@link #VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}.
+ * @param handler The handler on which the listener should be invoked, or null
+ * if the listener should be invoked on the calling thread's looper.
+ * @param callback Callback to call when the state of the {@link VirtualDisplay} changes
+ * @return The newly created virtual display, or null if the application could
+ * not create the virtual display.
+ *
+ * @throws SecurityException if the caller does not have permission to create
+ * a virtual display with the specified flags.
+ */
+ @Nullable
+ public VirtualDisplay createVirtualDisplay(@NonNull String name,
+ @IntRange(from = 1) int width,
+ @IntRange(from = 1) int height,
+ @IntRange(from = 1) int densityDpi,
+ float requestedRefreshRate,
+ @Nullable Surface surface,
+ @VirtualDisplayFlag int flags,
+ @Nullable Handler handler,
+ @Nullable VirtualDisplay.Callback callback) {
+ if (!ENABLE_VIRTUAL_DISPLAY_REFRESH_RATE && requestedRefreshRate != 0.0f) {
+ Slog.e(TAG, "Please turn on ENABLE_VIRTUAL_DISPLAY_REFRESH_RATE to use the new api");
+ return null;
+ }
+
final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
height, densityDpi);
builder.setFlags(flags);
if (surface != null) {
builder.setSurface(surface);
}
+ if (requestedRefreshRate != 0.0f) {
+ builder.setRequestedRefreshRate(requestedRefreshRate);
+ }
return createVirtualDisplay(null /* projection */, builder.build(), callback, handler,
null /* windowContext */);
}
diff --git a/core/java/android/hardware/display/HdrConversionMode.java b/core/java/android/hardware/display/HdrConversionMode.java
index 1accd17..da2b016 100644
--- a/core/java/android/hardware/display/HdrConversionMode.java
+++ b/core/java/android/hardware/display/HdrConversionMode.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -119,4 +120,40 @@
dest.writeInt(mConversionMode);
dest.writeInt(mPreferredHdrOutputType);
}
-}
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ return o instanceof HdrConversionMode && equals((HdrConversionMode) o);
+ }
+
+ @Override
+ public int hashCode() {
+ return 0; // don't care
+ }
+
+ @Override
+ public String toString() {
+ return "HdrConversionMode{ConversionMode=" + hdrConversionModeString(getConversionMode())
+ + ", PreferredHdrOutputType="
+ + Display.HdrCapabilities.hdrTypeToString(getPreferredHdrOutputType()) + "}";
+ }
+
+ private boolean equals(HdrConversionMode other) {
+ return other != null
+ && mConversionMode == other.getConversionMode()
+ && mPreferredHdrOutputType == other.getPreferredHdrOutputType();
+ }
+
+ private static String hdrConversionModeString(int hdrConversionMode) {
+ switch (hdrConversionMode) {
+ case HDR_CONVERSION_PASSTHROUGH:
+ return "HDR_CONVERSION_PASSTHROUGH";
+ case HDR_CONVERSION_SYSTEM:
+ return "HDR_CONVERSION_SYSTEM";
+ case HDR_CONVERSION_FORCE:
+ return "HDR_CONVERSION_FORCE";
+ default:
+ return "HDR_CONVERSION_UNKNOWN";
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index abd647f..f6a2e33 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -109,6 +109,13 @@
@DataClass.PluralOf("displayCategory")
@NonNull private List<String> mDisplayCategories = new ArrayList<>();
+ /**
+ * The refresh rate of a virtual display in frames per second.
+ * If this value is non-zero, this is the requested refresh rate to set.
+ * If this value is zero, the system chooses a default refresh rate.
+ */
+ private float mRequestedRefreshRate = 0.0f;
+
// Code below generated by codegen v1.0.23.
@@ -135,7 +142,8 @@
@Nullable String uniqueId,
int displayIdToMirror,
boolean windowManagerMirroring,
- @NonNull List<String> displayCategories) {
+ @NonNull List<String> displayCategories,
+ float requestedRefreshRate) {
this.mName = name;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mName);
@@ -161,6 +169,7 @@
this.mDisplayCategories = displayCategories;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mDisplayCategories);
+ this.mRequestedRefreshRate = requestedRefreshRate;
// onConstructed(); // You can define this method to get a callback
}
@@ -256,6 +265,16 @@
return mDisplayCategories;
}
+ /**
+ * The refresh rate of a virtual display in frames per second.
+ * If this value is none zero, this is the requested refresh rate to set.
+ * If this value is zero, the system chooses a default refresh rate.
+ */
+ @DataClass.Generated.Member
+ public float getRequestedRefreshRate() {
+ return mRequestedRefreshRate;
+ }
+
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -276,6 +295,7 @@
if (mUniqueId != null) dest.writeString(mUniqueId);
dest.writeInt(mDisplayIdToMirror);
dest.writeStringList(mDisplayCategories);
+ dest.writeFloat(mRequestedRefreshRate);
}
@Override
@@ -301,6 +321,7 @@
int displayIdToMirror = in.readInt();
List<String> displayCategories = new ArrayList<>();
in.readStringList(displayCategories);
+ float requestedRefreshRate = in.readFloat();
this.mName = name;
com.android.internal.util.AnnotationValidations.validate(
@@ -327,6 +348,7 @@
this.mDisplayCategories = displayCategories;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mDisplayCategories);
+ this.mRequestedRefreshRate = requestedRefreshRate;
// onConstructed(); // You can define this method to get a callback
}
@@ -362,6 +384,7 @@
private int mDisplayIdToMirror;
private boolean mWindowManagerMirroring;
private @NonNull List<String> mDisplayCategories;
+ private float mRequestedRefreshRate;
private long mBuilderFieldsSet = 0L;
@@ -528,10 +551,23 @@
return this;
}
+ /**
+ * The refresh rate of a virtual display in frames per second.
+ * If this value is none zero, this is the requested refresh rate to set.
+ * If this value is zero, the system chooses a default refresh rate.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setRequestedRefreshRate(float value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x400;
+ mRequestedRefreshRate = value;
+ return this;
+ }
+
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull VirtualDisplayConfig build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x400; // Mark builder used
+ mBuilderFieldsSet |= 0x800; // Mark builder used
if ((mBuilderFieldsSet & 0x10) == 0) {
mFlags = 0;
@@ -551,6 +587,9 @@
if ((mBuilderFieldsSet & 0x200) == 0) {
mDisplayCategories = new ArrayList<>();
}
+ if ((mBuilderFieldsSet & 0x400) == 0) {
+ mRequestedRefreshRate = 0.0f;
+ }
VirtualDisplayConfig o = new VirtualDisplayConfig(
mName,
mWidth,
@@ -561,12 +600,13 @@
mUniqueId,
mDisplayIdToMirror,
mWindowManagerMirroring,
- mDisplayCategories);
+ mDisplayCategories,
+ mRequestedRefreshRate);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x400) != 0) {
+ if ((mBuilderFieldsSet & 0x800) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -574,10 +614,10 @@
}
@DataClass.Generated(
- time = 1668534501320L,
+ time = 1671047069703L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java",
- inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange int mWidth\nprivate @android.annotation.IntRange int mHeight\nprivate @android.annotation.IntRange int mDensityDpi\nprivate @android.hardware.display.DisplayManager.VirtualDisplayFlag int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate int mDisplayIdToMirror\nprivate boolean mWindowManagerMirroring\nprivate @com.android.internal.util.DataClass.PluralOf(\"displayCategory\") @android.annotation.NonNull java.util.List<java.lang.String> mDisplayCategories\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)")
+ inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange int mWidth\nprivate @android.annotation.IntRange int mHeight\nprivate @android.annotation.IntRange int mDensityDpi\nprivate @android.hardware.display.DisplayManager.VirtualDisplayFlag int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate int mDisplayIdToMirror\nprivate boolean mWindowManagerMirroring\nprivate @com.android.internal.util.DataClass.PluralOf(\"displayCategory\") @android.annotation.NonNull java.util.List<java.lang.String> mDisplayCategories\nprivate float mRequestedRefreshRate\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/hardware/hdmi/DeviceFeatures.java b/core/java/android/hardware/hdmi/DeviceFeatures.java
index dc530ff..3805b55 100644
--- a/core/java/android/hardware/hdmi/DeviceFeatures.java
+++ b/core/java/android/hardware/hdmi/DeviceFeatures.java
@@ -130,12 +130,12 @@
byte result = 0;
if (mRecordTvScreenSupport == FEATURE_SUPPORTED) result |= (byte) (1 << 6);
- if (mSetOsdStringSupport == FEATURE_SUPPORTED) result = (byte) (1 << 5);
- if (mDeckControlSupport == FEATURE_SUPPORTED) result = (byte) (1 << 4);
- if (mSetAudioRateSupport == FEATURE_SUPPORTED) result = (byte) (1 << 3);
- if (mArcTxSupport == FEATURE_SUPPORTED) result = (byte) (1 << 2);
- if (mArcRxSupport == FEATURE_SUPPORTED) result = (byte) (1 << 1);
- if (mSetAudioVolumeLevelSupport == FEATURE_SUPPORTED) result = (byte) 1;
+ if (mSetOsdStringSupport == FEATURE_SUPPORTED) result |= (byte) (1 << 5);
+ if (mDeckControlSupport == FEATURE_SUPPORTED) result |= (byte) (1 << 4);
+ if (mSetAudioRateSupport == FEATURE_SUPPORTED) result |= (byte) (1 << 3);
+ if (mArcTxSupport == FEATURE_SUPPORTED) result |= (byte) (1 << 2);
+ if (mArcRxSupport == FEATURE_SUPPORTED) result |= (byte) (1 << 1);
+ if (mSetAudioVolumeLevelSupport == FEATURE_SUPPORTED) result |= (byte) 1;
return new byte[]{ result };
}
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index 222e88f..2d2b0fc 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -16,6 +16,7 @@
package android.os;
+import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.FloatRange;
import android.annotation.IntDef;
@@ -69,7 +70,10 @@
* An interface describing the callback for bugreport progress and status.
*
* <p>Callers will receive {@link #onProgress} calls as the bugreport progresses, followed by a
- * terminal call to either {@link #onFinished} or {@link #onError}.
+ * terminal call to either {@link #onFinished} or {@link #onError}. Note that
+ * {@link #onFinished(String)} will only be invoked when calling {@code startBugreport} with the
+ * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag set. Otherwise,
+ * {@link #onFinished()} will be invoked.
*
* <p>If an issue is encountered while starting the bugreport asynchronously, callers will
* receive an {@link #onError} call without any {@link #onProgress} callbacks.
@@ -88,7 +92,8 @@
BUGREPORT_ERROR_RUNTIME,
BUGREPORT_ERROR_USER_DENIED_CONSENT,
BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT,
- BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS
+ BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS,
+ BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE
})
public @interface BugreportErrorCode {}
@@ -115,6 +120,10 @@
public static final int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS =
IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS;
+ /** There is no bugreport to retrieve for the caller. */
+ public static final int BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE =
+ IDumpstateListener.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE;
+
/**
* Called when there is a progress update.
*
@@ -137,9 +146,25 @@
*/
public void onError(@BugreportErrorCode int errorCode) {}
- /** Called when taking bugreport finishes successfully. */
+ /** Called when taking bugreport finishes successfully.
+ *
+ * <p>This callback will be invoked if the
+ * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set. Otherwise, the
+ * {@link #onFinished(String)} callback will be invoked.
+ */
public void onFinished() {}
+ /** Called when taking bugreport finishes successfully.
+ *
+ * <p>This callback will only be invoked if the
+ * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is set. Otherwise, the
+ * {@link #onFinished()} callback will be invoked.
+ *
+ * @param bugreportFile the absolute path of the generated bugreport file.
+
+ */
+ public void onFinished(@NonNull String bugreportFile) {}
+
/**
* Called when it is ready for calling app to show UI, showing any extra UI before this
* callback can interfere with bugreport generation.
@@ -178,7 +203,9 @@
* updates.
*
* <p>The bugreport artifacts will be copied over to the given file descriptors only if the user
- * consents to sharing with the calling app.
+ * consents to sharing with the calling app. If
+ * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} is set, user consent will be deferred
+ * and no files will be copied to the given file descriptors.
*
* <p>{@link BugreportManager} takes ownership of {@code bugreportFd} and {@code screenshotFd}.
*
@@ -205,7 +232,9 @@
Preconditions.checkNotNull(executor);
Preconditions.checkNotNull(callback);
- boolean isScreenshotRequested = screenshotFd != null;
+ boolean deferConsent =
+ (params.getFlags() & BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT) != 0;
+ boolean isScreenshotRequested = screenshotFd != null || deferConsent;
if (screenshotFd == null) {
// Binder needs a valid File Descriptor to be passed
screenshotFd =
@@ -213,7 +242,7 @@
new File("/dev/null"), ParcelFileDescriptor.MODE_READ_ONLY);
}
DumpstateListener dsListener =
- new DumpstateListener(executor, callback, isScreenshotRequested);
+ new DumpstateListener(executor, callback, isScreenshotRequested, deferConsent);
// Note: mBinder can get callingUid from the binder transaction.
mBinder.startBugreport(
-1 /* callingUid */,
@@ -238,6 +267,58 @@
}
/**
+ * Retrieves a previously generated bugreport.
+ *
+ * <p>The previously generated bugreport must have been generated by calling {@link
+ * #startBugreport(ParcelFileDescriptor, ParcelFileDescriptor, BugreportParams,
+ * Executor, BugreportCallback)} with the {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT}
+ * flag set. The bugreport file returned by the {@link BugreportCallback#onFinished(String)}
+ * callback for a previously generated bugreport must be passed to this method. A caller may
+ * only retrieve bugreports that they have previously requested.
+ *
+ * <p>The bugreport artifacts will be copied over to the given file descriptor only if the user
+ * consents to sharing with the calling app.
+ *
+ * <p>{@link BugreportManager} takes ownership of {@code bugreportFd}.
+ *
+ * <p>The caller may only request to retrieve a given bugreport once. Subsequent calls will fail
+ * with error code {@link BugreportCallback#BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE}.
+ *
+ * @param bugreportFile the identifier for a bugreport that was previously generated for this
+ * caller using {@code startBugreport}.
+ * @param bugreportFd file to copy over the previous bugreport. This should be opened in
+ * write-only, append mode.
+ * @param executor the executor to execute callback methods.
+ * @param callback callback for progress and status updates.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.DUMP)
+ @WorkerThread
+ public void retrieveBugreport(
+ @NonNull String bugreportFile,
+ @NonNull ParcelFileDescriptor bugreportFd,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull BugreportCallback callback
+ ) {
+ try {
+ Preconditions.checkNotNull(bugreportFile);
+ Preconditions.checkNotNull(bugreportFd);
+ Preconditions.checkNotNull(executor);
+ Preconditions.checkNotNull(callback);
+ DumpstateListener dsListener = new DumpstateListener(executor, callback, false, false);
+ mBinder.retrieveBugreport(Binder.getCallingUid(), mContext.getOpPackageName(),
+ bugreportFd.getFileDescriptor(),
+ bugreportFile,
+ dsListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } finally {
+ IoUtils.closeQuietly(bugreportFd);
+ }
+ }
+
+ /**
* Starts a connectivity bugreport.
*
* <p>The connectivity bugreport is a specialized version of bugreport that only includes
@@ -316,7 +397,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.DUMP)
+ @RequiresPermission(Manifest.permission.DUMP)
public void requestBugreport(
@NonNull BugreportParams params,
@Nullable CharSequence shareTitle,
@@ -335,12 +416,15 @@
private final Executor mExecutor;
private final BugreportCallback mCallback;
private final boolean mIsScreenshotRequested;
+ private final boolean mIsConsentDeferred;
DumpstateListener(
- Executor executor, BugreportCallback callback, boolean isScreenshotRequested) {
+ Executor executor, BugreportCallback callback, boolean isScreenshotRequested,
+ boolean isConsentDeferred) {
mExecutor = executor;
mCallback = callback;
mIsScreenshotRequested = isScreenshotRequested;
+ mIsConsentDeferred = isConsentDeferred;
}
@Override
@@ -364,10 +448,14 @@
}
@Override
- public void onFinished() throws RemoteException {
+ public void onFinished(String bugreportFile) throws RemoteException {
final long identity = Binder.clearCallingIdentity();
try {
- mExecutor.execute(() -> mCallback.onFinished());
+ if (mIsConsentDeferred) {
+ mExecutor.execute(() -> mCallback.onFinished(bugreportFile));
+ } else {
+ mExecutor.execute(() -> mCallback.onFinished());
+ }
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
index 990883f..d9d14b0 100644
--- a/core/java/android/os/BugreportParams.java
+++ b/core/java/android/os/BugreportParams.java
@@ -21,6 +21,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
/**
* Parameters that specify what kind of bugreport should be taken.
@@ -125,7 +126,8 @@
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "BUGREPORT_FLAG_" }, value = {
- BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA
+ BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA,
+ BUGREPORT_FLAG_DEFER_CONSENT
})
public @interface BugreportFlag {}
@@ -135,4 +137,14 @@
*/
public static final int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA =
IDumpstate.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA;
+
+ /**
+ * Flag for deferring user consent.
+ *
+ * <p>This flag should be used in cases where it may not be possible for the user to respond
+ * to a consent dialog immediately, such as when the user is driving. The generated bugreport
+ * may be retrieved at a later time using {@link BugreportManager#retrieveBugreport(
+ * String, ParcelFileDescriptor, Executor, BugreportManager.BugreportCallback)}.
+ */
+ public static final int BUGREPORT_FLAG_DEFER_CONSENT = IDumpstate.BUGREPORT_FLAG_DEFER_CONSENT;
}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 249f486..832f23c 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -61,6 +61,17 @@
/** The name of the overall product. */
public static final String PRODUCT = getString("ro.product.name");
+ /**
+ * The product name for attestation. In non-default builds (like the AOSP build) the value of
+ * the 'PRODUCT' system property may be different to the one provisioned to KeyMint,
+ * and Keymint attestation would still attest to the product name, it's running on.
+ * @hide
+ */
+ @Nullable
+ @TestApi
+ public static final String PRODUCT_FOR_ATTESTATION =
+ getString("ro.product.name_for_attestation");
+
/** The name of the industrial design. */
public static final String DEVICE = getString("ro.product.device");
@@ -89,9 +100,31 @@
/** The consumer-visible brand with which the product/hardware will be associated, if any. */
public static final String BRAND = getString("ro.product.brand");
+ /**
+ * The product brand for attestation. In non-default builds (like the AOSP build) the value of
+ * the 'BRAND' system property may be different to the one provisioned to KeyMint,
+ * and Keymint attestation would still attest to the product brand, it's running on.
+ * @hide
+ */
+ @Nullable
+ @TestApi
+ public static final String BRAND_FOR_ATTESTATION =
+ getString("ro.product.brand_for_attestation");
+
/** The end-user-visible name for the end product. */
public static final String MODEL = getString("ro.product.model");
+ /**
+ * The product model for attestation. In non-default builds (like the AOSP build) the value of
+ * the 'MODEL' system property may be different to the one provisioned to KeyMint,
+ * and Keymint attestation would still attest to the product model, it's running on.
+ * @hide
+ */
+ @Nullable
+ @TestApi
+ public static final String MODEL_FOR_ATTESTATION =
+ getString("ro.product.model_for_attestation");
+
/** The manufacturer of the device's primary system-on-chip. */
@NonNull
public static final String SOC_MANUFACTURER = SocProperties.soc_manufacturer().orElse(UNKNOWN);
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index adc73c8..0d0d1da 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -110,7 +110,8 @@
public static final long TRACE_TAG_THERMAL = 1L << 27;
private static final long TRACE_TAG_NOT_READY = 1L << 63;
- private static final int MAX_SECTION_NAME_LEN = 127;
+ /** @hide **/
+ public static final int MAX_SECTION_NAME_LEN = 127;
// Must be volatile to avoid word tearing.
// This is only kept in case any apps get this by reflection but do not
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index d63d87d..0efd264 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2976,6 +2976,29 @@
}
/**
+ * @hide
+ */
+ public static boolean isVisibleBackgroundUsersOnDefaultDisplayEnabled() {
+ return SystemProperties.getBoolean("fw.visible_bg_users_on_default_display",
+ Resources.getSystem()
+ .getBoolean(R.bool.config_multiuserVisibleBackgroundUsersOnDefaultDisplay));
+ }
+
+ /**
+ * Returns whether the device allows (full) users to be started in background visible in the
+ * {@link android.view.Display#DEFAULT_DISPLAY default display}.
+ *
+ * @return {@code false} for most devices, except passenger-only automotive build (i.e., when
+ * Android runs in a separate system in the back seat to manage the passenger displays).
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean isVisibleBackgroundUsersOnDefaultDisplaySupported() {
+ return isVisibleBackgroundUsersOnDefaultDisplayEnabled();
+ }
+
+ /**
* Checks if the user is visible at the moment.
*
* <p>Roughly speaking, a "visible user" is a user that can present UI on at least one display.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b2d89962..653998f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7392,6 +7392,9 @@
*
* Format like "ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0"
* where imeId is ComponentName and subtype is int32.
+ *
+ * <p>Note: This setting is not readable to the app targeting API level 34 or higher. use
+ * {@link android.view.inputmethod.InputMethodManager#getEnabledInputMethodList()} instead.
*/
@Readable(maxTargetSdk = Build.VERSION_CODES.TIRAMISU)
public static final String ENABLED_INPUT_METHODS = "enabled_input_methods";
@@ -15498,6 +15501,25 @@
public static final String AUDIO_SAFE_VOLUME_STATE = "audio_safe_volume_state";
/**
+ * Persisted safe hearding current CSD value. Values are stored as float percentages where
+ * 1.f represents 100% sound dose has been reached.
+ * @hide
+ */
+ public static final String AUDIO_SAFE_CSD_CURRENT_VALUE = "audio_safe_csd_current_value";
+
+ /**
+ * Persisted safe hearding next CSD warning value. Values are stored as float percentages.
+ * @hide
+ */
+ public static final String AUDIO_SAFE_CSD_NEXT_WARNING = "audio_safe_csd_next_warning";
+
+ /**
+ * Persisted safe hearding dose records (see {@link android.media.SoundDoseRecord})
+ * @hide
+ */
+ public static final String AUDIO_SAFE_CSD_DOSE_RECORDS = "audio_safe_csd_dose_records";
+
+ /**
* URL for tzinfo (time zone) updates
* @hide
*/
@@ -15879,6 +15901,36 @@
"user_preferred_resolution_width";
/**
+ * The HDR output mode chosen by the user. This is one of:
+ * {@link android.hardware.display.HdrConversionMode#HDR_CONVERSION_PASSTHROUGH},
+ * {@link android.hardware.display.HdrConversionMode#HDR_CONVERSION_SYSTEM},
+ * {@link android.hardware.display.HdrConversionMode#HDR_CONVERSION_FORCE}.
+ *
+ * @hide
+ */
+ @TestApi
+ @Readable
+ public static final String HDR_CONVERSION_MODE = "hdr_conversion_mode";
+
+ /**
+ * The output HDR type chosen by the user in case when {@link #HDR_CONVERSION_MODE} is
+ * {@link #HDR_CONVERSION_FORCE}. This is one of:
+ * {@link android.view.Display.HdrCapabilities#HDR_TYPE_INVALID},
+ * {@link android.view.Display.HdrCapabilities#HDR_TYPE_DOLBY_VISION},
+ * {@link android.view.Display.HdrCapabilities#HDR_TYPE_HDR10},
+ * {@link android.view.Display.HdrCapabilities#HDR_TYPE_HLG},
+ * {@link android.view.Display.HdrCapabilities#HDR_TYPE_HDR10_PLUS}
+ * <p>
+ * The value is {@link android.view.Display.HdrCapabilities#HDR_TYPE_INVALID} when user
+ * chooses SDR output type. </p>
+ *
+ * @hide
+ */
+ @TestApi
+ @Readable
+ public static final String HDR_FORCE_CONVERSION_TYPE = "hdr_force_conversion_type";
+
+ /**
* The name of the device
*/
@Readable
diff --git a/core/java/android/service/credentials/BeginGetCredentialRequest.java b/core/java/android/service/credentials/BeginGetCredentialRequest.java
index e375cdd..5d040db 100644
--- a/core/java/android/service/credentials/BeginGetCredentialRequest.java
+++ b/core/java/android/service/credentials/BeginGetCredentialRequest.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.app.PendingIntent;
import android.content.Intent;
-import android.credentials.GetCredentialOption;
import android.os.Parcel;
import android.os.Parcelable;
@@ -33,7 +32,7 @@
/**
* Query stage request for getting user's credentials from a given credential provider.
*
- * <p>This request contains a list of {@link GetCredentialOption} that have parameters
+ * <p>This request contains a list of {@link BeginGetCredentialOption} that have parameters
* to be used to query credentials, and return a list of {@link CredentialEntry} to be set
* on the {@link BeginGetCredentialResponse}. This list is then shown to the user on a selector.
*
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index ee386c3..f92fd3a 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -308,7 +308,7 @@
*
* <p>This API denotes a query stage request for getting user's credentials from a given
* credential provider. The request contains a list of
- * {@link android.credentials.GetCredentialOption} that have parameters to be used for
+ * {@link BeginGetCredentialOption} that have parameters to be used for
* populating candidate credentials, as a list of {@link CredentialEntry} to be set
* on the {@link BeginGetCredentialResponse}. This list is then shown to the user on a
* selector.
diff --git a/core/java/android/service/credentials/GetCredentialRequest.java b/core/java/android/service/credentials/GetCredentialRequest.java
index 4b946f0..e808ace 100644
--- a/core/java/android/service/credentials/GetCredentialRequest.java
+++ b/core/java/android/service/credentials/GetCredentialRequest.java
@@ -17,7 +17,7 @@
package android.service.credentials;
import android.annotation.NonNull;
-import android.credentials.GetCredentialOption;
+import android.credentials.CredentialOption;
import android.os.Parcel;
import android.os.Parcelable;
@@ -32,28 +32,30 @@
*/
public final class GetCredentialRequest implements Parcelable {
/** Calling package of the app requesting for credentials. */
- private final @NonNull CallingAppInfo mCallingAppInfo;
+ @NonNull
+ private final CallingAppInfo mCallingAppInfo;
/**
* Holds parameters to be used for retrieving a specific type of credential.
*/
- private final @NonNull GetCredentialOption mGetCredentialOption;
+ @NonNull
+ private final CredentialOption mCredentialOption;
public GetCredentialRequest(@NonNull CallingAppInfo callingAppInfo,
- @NonNull GetCredentialOption getCredentialOption) {
+ @NonNull CredentialOption credentialOption) {
this.mCallingAppInfo = callingAppInfo;
- this.mGetCredentialOption = getCredentialOption;
+ this.mCredentialOption = credentialOption;
}
private GetCredentialRequest(@NonNull Parcel in) {
mCallingAppInfo = in.readTypedObject(CallingAppInfo.CREATOR);
AnnotationValidations.validate(NonNull.class, null, mCallingAppInfo);
- mGetCredentialOption = in.readTypedObject(GetCredentialOption.CREATOR);
- AnnotationValidations.validate(NonNull.class, null, mGetCredentialOption);
+ mCredentialOption = in.readTypedObject(CredentialOption.CREATOR);
+ AnnotationValidations.validate(NonNull.class, null, mCredentialOption);
}
- public static final @NonNull Creator<GetCredentialRequest> CREATOR =
- new Creator<GetCredentialRequest>() {
+ @NonNull public static final Creator<GetCredentialRequest> CREATOR =
+ new Creator<>() {
@Override
public GetCredentialRequest createFromParcel(Parcel in) {
return new GetCredentialRequest(in);
@@ -73,20 +75,22 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeTypedObject(mCallingAppInfo, flags);
- dest.writeTypedObject(mGetCredentialOption, flags);
+ dest.writeTypedObject(mCredentialOption, flags);
}
/**
* Returns info pertaining to the app requesting credentials.
*/
- public @NonNull CallingAppInfo getCallingAppInfo() {
+ @NonNull
+ public CallingAppInfo getCallingAppInfo() {
return mCallingAppInfo;
}
/**
* Returns the parameters needed to return a given type of credential.
*/
- public @NonNull GetCredentialOption getGetCredentialOption() {
- return mGetCredentialOption;
+ @NonNull
+ public CredentialOption getGetCredentialOption() {
+ return mCredentialOption;
}
}
diff --git a/core/java/android/service/search/ISearchUiService.aidl b/core/java/android/service/search/ISearchUiService.aidl
index aae66ca..bc6d421 100644
--- a/core/java/android/service/search/ISearchUiService.aidl
+++ b/core/java/android/service/search/ISearchUiService.aidl
@@ -37,5 +37,11 @@
void onNotifyEvent(in SearchSessionId sessionId, in Query input, in SearchTargetEvent event);
+ void onRegisterEmptyQueryResultUpdateCallback (in SearchSessionId sessionId, in ISearchCallback callback);
+
+ void onRequestEmptyQueryResultUpdate(in SearchSessionId sessionId);
+
+ void onUnregisterEmptyQueryResultUpdateCallback(in SearchSessionId sessionId, in ISearchCallback callback);
+
void onDestroy(in SearchSessionId sessionId);
}
diff --git a/core/java/android/service/search/SearchUiService.java b/core/java/android/service/search/SearchUiService.java
index 02d41ef..55a96fa 100644
--- a/core/java/android/service/search/SearchUiService.java
+++ b/core/java/android/service/search/SearchUiService.java
@@ -20,6 +20,7 @@
import android.annotation.CallSuper;
import android.annotation.MainThread;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.Service;
import android.app.search.ISearchCallback;
@@ -35,8 +36,10 @@
import android.os.Looper;
import android.os.RemoteException;
import android.service.search.ISearchUiService.Stub;
+import android.util.ArrayMap;
import android.util.Slog;
+import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@@ -66,6 +69,9 @@
public static final String SERVICE_INTERFACE =
"android.service.search.SearchUiService";
+ private final ArrayMap<SearchSessionId, ArrayList<CallbackWrapper>>
+ mSessionEmptyQueryResultCallbacks = new ArrayMap<>();
+
private Handler mHandler;
private final android.service.search.ISearchUiService mInterface = new Stub() {
@@ -87,7 +93,7 @@
mHandler.sendMessage(
obtainMessage(SearchUiService::onQuery,
SearchUiService.this, sessionId, input,
- new CallbackWrapper(callback)));
+ new CallbackWrapper(callback, null)));
}
@Override
@@ -98,6 +104,28 @@
}
@Override
+ public void onRegisterEmptyQueryResultUpdateCallback(SearchSessionId sessionId,
+ ISearchCallback callback) {
+ mHandler.sendMessage(
+ obtainMessage(SearchUiService::doRegisterEmptyQueryResultUpdateCallback,
+ SearchUiService.this, sessionId, callback));
+ }
+
+ @Override
+ public void onRequestEmptyQueryResultUpdate(SearchSessionId sessionId) {
+ mHandler.sendMessage(obtainMessage(SearchUiService::doRequestEmptyQueryResultUpdate,
+ SearchUiService.this, sessionId));
+ }
+
+ @Override
+ public void onUnregisterEmptyQueryResultUpdateCallback(SearchSessionId sessionId,
+ ISearchCallback callback) {
+ mHandler.sendMessage(
+ obtainMessage(SearchUiService::doUnregisterEmptyQueryResultUpdateCallback,
+ SearchUiService.this, sessionId, callback));
+ }
+
+ @Override
public void onDestroy(SearchSessionId sessionId) {
mHandler.sendMessage(
obtainMessage(SearchUiService::doDestroy,
@@ -126,21 +154,23 @@
/**
* Creates a new search session.
*
+ * @removed
* @deprecated this is method will be removed as soon as
* {@link #onSearchSessionCreated(SearchContext, SearchSessionId)}
* is adopted by the service.
- *
- * @removed
*/
@Deprecated
public void onCreateSearchSession(@NonNull SearchContext context,
- @NonNull SearchSessionId sessionId) {}
+ @NonNull SearchSessionId sessionId) {
+ }
/**
* A new search session is created.
*/
public void onSearchSessionCreated(@NonNull SearchContext context,
- @NonNull SearchSessionId sessionId) {}
+ @NonNull SearchSessionId sessionId) {
+ mSessionEmptyQueryResultCallbacks.put(sessionId, new ArrayList<>());
+ }
/**
* Called by the client to request search results using a query string.
@@ -161,6 +191,98 @@
@NonNull Query query,
@NonNull SearchTargetEvent event);
+ private void doRegisterEmptyQueryResultUpdateCallback(@NonNull SearchSessionId sessionId,
+ @NonNull ISearchCallback callback) {
+ final ArrayList<CallbackWrapper> callbacks = mSessionEmptyQueryResultCallbacks.get(
+ sessionId);
+ if (callbacks == null) {
+ Slog.e(TAG, "Failed to register for updates for unknown session: " + sessionId);
+ return;
+ }
+
+ final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback);
+ if (wrapper == null) {
+ callbacks.add(new CallbackWrapper(callback,
+ callbackWrapper ->
+ mHandler.post(() ->
+ removeCallbackWrapper(callbacks, callbackWrapper))));
+ if (callbacks.size() == 1) {
+ onStartUpdateEmptyQueryResult();
+ }
+ }
+ }
+
+ /**
+ * Called when the first empty query result callback is registered. Service provider may make
+ * their own decision whether to generate data if no callback is registered to optimize for
+ * system health.
+ */
+ @MainThread
+ public void onStartUpdateEmptyQueryResult() {}
+
+ private void doRequestEmptyQueryResultUpdate(@NonNull SearchSessionId sessionId) {
+ // Just an optimization, if there are no callbacks, then don't bother notifying the service
+ final ArrayList<CallbackWrapper> callbacks = mSessionEmptyQueryResultCallbacks.get(
+ sessionId);
+ if (callbacks != null && !callbacks.isEmpty()) {
+ onRequestEmptyQueryResultUpdate(sessionId);
+ }
+ }
+
+ /**
+ * Called by a client to request empty query search target result for zero state. This method
+ * is only called if there are one or more empty query result update callbacks registered.
+ *
+ * @see #updateEmptyQueryResult(SearchSessionId, List)
+ */
+ @MainThread
+ public void onRequestEmptyQueryResultUpdate(@NonNull SearchSessionId sessionId) {}
+
+ private void doUnregisterEmptyQueryResultUpdateCallback(@NonNull SearchSessionId sessionId,
+ @NonNull ISearchCallback callback) {
+ final ArrayList<CallbackWrapper> callbacks = mSessionEmptyQueryResultCallbacks.get(
+ sessionId);
+ if (callbacks == null) {
+ Slog.e(TAG, "Failed to unregister for updates for unknown session: " + sessionId);
+ return;
+ }
+
+ final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback);
+ removeCallbackWrapper(callbacks, wrapper);
+ }
+
+ /**
+ * Finds the callback wrapper for the given callback.
+ */
+ private CallbackWrapper findCallbackWrapper(ArrayList<CallbackWrapper> callbacks,
+ ISearchCallback callback) {
+ for (int i = callbacks.size() - 1; i >= 0; i--) {
+ if (callbacks.get(i).isCallback(callback)) {
+ return callbacks.get(i);
+ }
+ }
+ return null;
+ }
+
+ private void removeCallbackWrapper(@Nullable ArrayList<CallbackWrapper> callbacks,
+ @Nullable CallbackWrapper wrapper) {
+ if (callbacks == null || wrapper == null) {
+ return;
+ }
+ callbacks.remove(wrapper);
+ wrapper.destroy();
+ if (callbacks.isEmpty()) {
+ onStopUpdateEmptyQueryResult();
+ }
+ }
+
+ /**
+ * Called when there are no longer any empty query result callbacks registered. Service
+ * provider can choose to stop generating data to optimize for system health.
+ */
+ @MainThread
+ public void onStopUpdateEmptyQueryResult() {}
+
private void doDestroy(@NonNull SearchSessionId sessionId) {
super.onDestroy();
onDestroy(sessionId);
@@ -172,14 +294,49 @@
@MainThread
public abstract void onDestroy(@NonNull SearchSessionId sessionId);
- private static final class CallbackWrapper implements Consumer<List<SearchTarget>> {
+ /**
+ * Used by the service provider to send back results the client app. The can be called
+ * in response to {@link #onRequestEmptyQueryResultUpdate(SearchSessionId)} or proactively as
+ * a result of changes in zero state data.
+ */
+ public final void updateEmptyQueryResult(@NonNull SearchSessionId sessionId,
+ @NonNull List<SearchTarget> targets) {
+ List<CallbackWrapper> callbacks = mSessionEmptyQueryResultCallbacks.get(sessionId);
+ if (callbacks != null) {
+ for (CallbackWrapper callback : callbacks) {
+ callback.accept(targets);
+ }
+ }
+ }
+
+ private static final class CallbackWrapper implements Consumer<List<SearchTarget>>,
+ IBinder.DeathRecipient {
private ISearchCallback mCallback;
+ private final Consumer<CallbackWrapper> mOnBinderDied;
- CallbackWrapper(ISearchCallback callback) {
+ CallbackWrapper(ISearchCallback callback,
+ @Nullable Consumer<CallbackWrapper> onBinderDied) {
mCallback = callback;
+ mOnBinderDied = onBinderDied;
+ if (mOnBinderDied != null) {
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death:" + e);
+ }
+ }
}
+ public boolean isCallback(@NonNull ISearchCallback callback) {
+ if (mCallback == null) {
+ Slog.e(TAG, "Callback is null, likely the binder has died.");
+ return false;
+ }
+ return mCallback.asBinder().equals(callback.asBinder());
+ }
+
+
@Override
public void accept(List<SearchTarget> searchTargets) {
try {
@@ -193,5 +350,20 @@
Slog.e(TAG, "Error sending result:" + e);
}
}
+
+ public void destroy() {
+ if (mCallback != null && mOnBinderDied != null) {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ destroy();
+ mCallback = null;
+ if (mOnBinderDied != null) {
+ mOnBinderDied.accept(this);
+ }
+ }
}
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 44afb89..23513fad 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1242,7 +1242,7 @@
null /* ignoringVisibilityState */, config.isScreenRound(),
false /* alwaysConsumeSystemBars */, mLayout.softInputMode,
mLayout.flags, SYSTEM_UI_FLAG_VISIBLE, mLayout.type,
- config.windowConfiguration.getWindowingMode(), null /* typeSideMap */);
+ config.windowConfiguration.getWindowingMode(), null /* idSideMap */);
if (!fixedSize) {
final Rect padding = mIWallpaperEngine.mDisplayPadding;
diff --git a/core/java/android/text/Highlights.java b/core/java/android/text/Highlights.java
index 356dfca..ac4a5f2 100644
--- a/core/java/android/text/Highlights.java
+++ b/core/java/android/text/Highlights.java
@@ -38,9 +38,9 @@
}
/**
- * Returns a number of highlight.
+ * Returns the number of highlight.
*
- * @return a number of highlight.
+ * @return the number of highlight.
*
* @see Builder#addRange(Paint, int, int)
* @see Builder#addRanges(Paint, int...)
@@ -53,7 +53,7 @@
* Returns a paint used for the i-th highlight.
*
* @param index an index of the highlight. Must be between 0 and {@link #getSize()}
- * @return a paint object
+ * @return the paint object
*
* @see Builder#addRange(Paint, int, int)
* @see Builder#addRanges(Paint, int...)
@@ -72,7 +72,7 @@
* [1, 2, 3, 4].
*
* @param index an index of the highlight. Must be between 0 and {@link #getSize()}
- * @return a paint object
+ * @return the flattened ranges.
*
* @see Builder#addRange(Paint, int, int)
* @see Builder#addRanges(Paint, int...)
@@ -90,6 +90,9 @@
/**
* Add single range highlight.
*
+ * If the given range has overlaps with the already added ranges, the previous highlights
+ * are overdrawn by this range.
+ *
* @param paint a paint object used for drawing highlight path.
* @param start an inclusive offset of the text.
* @param end an exclusive offset of the text.
@@ -111,6 +114,9 @@
/**
* Add multiple ranges highlight.
*
+ * If the given ranges have overlap with the already added ranges, the previous highlights
+ * are overdrawn by this range.
+ *
* @param paint a paint object used for drawing highlight path.
* @param ranges a flatten ranges. The {@code 2 * i}-th element is an inclusive start offset
* of the {@code i}-th character. The {@code 2 * i + 1}-th element is an
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 06fe523..b98deba 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -205,7 +205,7 @@
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "false");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "false");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
- DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "false");
+ DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "true");
DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "false");
DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 25863a6..6b1499f 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -360,6 +360,17 @@
public static final int FLAG_STEAL_TOP_FOCUS_DISABLED = 1 << 12;
/**
+ * Display flag: Indicates that the display is a rear display.
+ * <p>
+ * This flag identifies complementary displays that are facing away from the user.
+ * </p>
+ *
+ * @hide
+ * @see #getFlags()
+ */
+ public static final int FLAG_REAR = 1 << 13;
+
+ /**
* Display flag: Indicates that the contents of the display should not be scaled
* to fit the physical screen dimensions. Used for development only to emulate
* devices with smaller physicals screens while preserving density.
@@ -2497,5 +2508,24 @@
+ ", mMaxAverageLuminance=" + mMaxAverageLuminance
+ ", mMinLuminance=" + mMinLuminance + '}';
}
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public static String hdrTypeToString(int hdrType) {
+ switch (hdrType) {
+ case HDR_TYPE_DOLBY_VISION:
+ return "HDR_TYPE_DOLBY_VISION";
+ case HDR_TYPE_HDR10:
+ return "HDR_TYPE_HDR10";
+ case HDR_TYPE_HLG:
+ return "HDR_TYPE_HLG";
+ case HDR_TYPE_HDR10_PLUS:
+ return "HDR_TYPE_HDR10_PLUS";
+ default:
+ return "HDR_TYPE_INVALID";
+ }
+ }
}
}
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index c2b6bc5..e26c7be 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -906,6 +906,9 @@
if ((flags & Display.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
result.append(", FLAG_TOUCH_FEEDBACK_DISABLED");
}
+ if ((flags & Display.FLAG_REAR) != 0) {
+ result.append(", FLAG_REAR_DISPLAY");
+ }
return result.toString();
}
}
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index e89be47..289a5b6 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -133,7 +133,8 @@
// If we had a request before to show from IME (tracked with mImeRequestedShow), reaching
// this code here means that we now got control, so we can start the animation immediately.
// If client window is trying to control IME and IME is already visible, it is immediate.
- if (fromIme || (mState.getSource(getId()).isVisible() && getControl() != null)) {
+ if (fromIme
+ || (mState.isSourceOrDefaultVisible(getId(), getType()) && getControl() != null)) {
return ShowResult.SHOW_IMMEDIATELY;
}
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index b22a43e..720f569 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -448,6 +448,7 @@
*/
public static final int KEYBOARD_TYPE_ALPHABETIC = 2;
+ // Cap motion ranges to prevent attacks (b/25637534)
private static final int MAX_RANGES = 1000;
private static final int VIBRATOR_ID_ALL = -1;
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 309a94a..75f1666 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -33,12 +33,12 @@
import static android.view.InsetsController.DEBUG;
import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
import static android.view.InsetsController.LayoutInsetsDuringAnimation;
+import static android.view.InsetsSource.ID_IME;
import static android.view.InsetsState.ISIDE_BOTTOM;
import static android.view.InsetsState.ISIDE_FLOATING;
import static android.view.InsetsState.ISIDE_LEFT;
import static android.view.InsetsState.ISIDE_RIGHT;
import static android.view.InsetsState.ISIDE_TOP;
-import static android.view.InsetsState.ITYPE_IME;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
@@ -132,19 +132,19 @@
mController = controller;
mInitialInsetsState = new InsetsState(state, true /* copySources */);
if (frame != null) {
- final SparseIntArray typeSideMap = new SparseIntArray();
- mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* typeSideMap */);
+ final SparseIntArray idSideMap = new SparseIntArray();
+ mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* idSideMap */);
mHiddenInsets = calculateInsets(mInitialInsetsState, frame, controls, false /* shown */,
- null /* typeSideMap */);
+ null /* idSideMap */);
mShownInsets = calculateInsets(mInitialInsetsState, frame, controls, true /* shown */,
- typeSideMap);
+ idSideMap);
mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsType(WindowInsets.Type.ime());
if (mHasZeroInsetsIme) {
// IME has shownInsets of ZERO, and can't map to a side by default.
// Map zero insets IME to bottom, making it a special case of bottom insets.
- typeSideMap.put(ITYPE_IME, ISIDE_BOTTOM);
+ idSideMap.put(ID_IME, ISIDE_BOTTOM);
}
- buildSideControlsMap(typeSideMap, mSideControlsMap, controls);
+ buildSideControlsMap(idSideMap, mSideControlsMap, controls);
} else {
// Passing a null frame indicates the caller wants to play the insets animation anyway,
// no matter the source provides insets to the frame or not.
@@ -399,27 +399,27 @@
}
private Insets getInsetsFromState(InsetsState state, Rect frame,
- @Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
+ @Nullable @InternalInsetsSide SparseIntArray idSideMap) {
return state.calculateInsets(frame, null /* ignoringVisibilityState */,
false /* isScreenRound */, false /* alwaysConsumeSystemBars */,
LayoutParams.SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode*/,
0 /* legacyWindowFlags */, 0 /* legacySystemUiFlags */, TYPE_APPLICATION,
- WINDOWING_MODE_UNDEFINED, typeSideMap).getInsets(mTypes);
+ WINDOWING_MODE_UNDEFINED, idSideMap).getInsets(mTypes);
}
/** Computes the insets relative to the given frame. */
private Insets calculateInsets(InsetsState state, Rect frame,
SparseArray<InsetsSourceControl> controls, boolean shown,
- @Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
+ @Nullable @InternalInsetsSide SparseIntArray idSideMap) {
for (int i = controls.size() - 1; i >= 0; i--) {
final InsetsSourceControl control = controls.valueAt(i);
if (control == null) {
// control may be null if it got revoked.
continue;
}
- state.getSource(control.getId()).setVisible(shown);
+ state.setSourceVisible(control.getId(), shown);
}
- return getInsetsFromState(state, frame, typeSideMap);
+ return getInsetsFromState(state, frame, idSideMap);
}
/** Computes the insets from the insets hints of controls. */
@@ -435,7 +435,8 @@
// control may be null if it got revoked.
continue;
}
- if (state == null || state.getSource(control.getId()).isVisible()) {
+ if (state == null
+ || state.isSourceOrDefaultVisible(control.getId(), control.getType())) {
insets = Insets.max(insets, control.getInsetsHint());
}
}
@@ -465,20 +466,23 @@
// TODO: Implement behavior when inset spans over multiple types
for (int i = controls.size() - 1; i >= 0; i--) {
final InsetsSourceControl control = controls.valueAt(i);
- final InsetsSource source = mInitialInsetsState.getSource(control.getId());
+ final InsetsSource source = mInitialInsetsState.peekSource(control.getId());
final SurfaceControl leash = control.getLeash();
mTmpMatrix.setTranslate(control.getSurfacePosition().x, control.getSurfacePosition().y);
- mTmpFrame.set(source.getFrame());
+ if (source != null) {
+ mTmpFrame.set(source.getFrame());
+ }
addTranslationToMatrix(side, offset, mTmpMatrix, mTmpFrame);
final boolean visible = mHasZeroInsetsIme && side == ISIDE_BOTTOM
? (mAnimationType == ANIMATION_TYPE_SHOW || !mFinished)
: inset != 0;
- if (outState != null) {
- outState.getSource(source.getId()).setVisible(visible);
- outState.getSource(source.getId()).setFrame(mTmpFrame);
+ if (outState != null && source != null) {
+ outState.getOrCreateSource(source.getId(), source.getType())
+ .setVisible(visible)
+ .setFrame(mTmpFrame);
}
// If the system is controlling the insets source, the leash can be null.
@@ -517,12 +521,12 @@
}
}
- private static void buildSideControlsMap(SparseIntArray typeSideMap,
+ private static void buildSideControlsMap(SparseIntArray idSideMap,
SparseSetArray<InsetsSourceControl> sideControlsMap,
SparseArray<InsetsSourceControl> controls) {
- for (int i = typeSideMap.size() - 1; i >= 0; i--) {
- final int type = typeSideMap.keyAt(i);
- final int side = typeSideMap.valueAt(i);
+ for (int i = idSideMap.size() - 1; i >= 0; i--) {
+ final int type = idSideMap.keyAt(i);
+ final int side = idSideMap.valueAt(i);
final InsetsSourceControl control = controls.get(type);
if (control == null) {
// If the types that we are controlling are less than the types that the system has,
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 8e8e28a..1c00e5f 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -19,12 +19,12 @@
import static android.os.Trace.TRACE_TAG_VIEW;
import static android.view.InsetsControllerProto.CONTROL;
import static android.view.InsetsControllerProto.STATE;
-import static android.view.InsetsState.ITYPE_CAPTION_BAR;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
import static android.view.WindowInsets.Type.FIRST;
import static android.view.WindowInsets.Type.LAST;
import static android.view.WindowInsets.Type.all;
+import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.ime;
import android.animation.AnimationHandler;
@@ -44,13 +44,12 @@
import android.os.Process;
import android.os.Trace;
import android.text.TextUtils;
-import android.util.ArraySet;
+import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsSourceConsumer.ShowResult;
-import android.view.InsetsState.InternalInsetsType;
import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
@@ -249,6 +248,9 @@
/** The amount IME will move up/down when animating in floating mode. */
private static final int FLOATING_IME_BOTTOM_INSET_DP = -80;
+ private static final int ID_CAPTION_BAR =
+ InsetsSource.createId(null /* owner */, 0 /* index */, captionBar());
+
static final boolean DEBUG = false;
static final boolean WARN = false;
@@ -621,6 +623,79 @@
private final Runnable mInvokeControllableInsetsChangedListeners =
this::invokeControllableInsetsChangedListeners;
+ private final InsetsState.OnTraverseCallbacks mRemoveGoneSources =
+ new InsetsState.OnTraverseCallbacks() {
+
+ private final IntArray mPendingRemoveIndexes = new IntArray();
+
+ @Override
+ public void onIdNotFoundInState2(int index1, InsetsSource source1) {
+ if (!CAPTION_ON_SHELL && source1.getType() == captionBar()) {
+ return;
+ }
+
+ // Don't change the indexes of the sources while traversing. Remove it later.
+ mPendingRemoveIndexes.add(index1);
+
+ // Remove the consumer as well except the IME one. IME consumer should always
+ // be there since we need to communicate with InputMethodManager no matter we
+ // have the source or not.
+ if (source1.getType() != ime()) {
+ mSourceConsumers.remove(source1.getId());
+ }
+ }
+
+ @Override
+ public void onFinish(InsetsState state1, InsetsState state2) {
+ for (int i = mPendingRemoveIndexes.size() - 1; i >= 0; i--) {
+ state1.removeSourceAt(mPendingRemoveIndexes.get(i));
+ }
+ mPendingRemoveIndexes.clear();
+ }
+ };
+
+ private final InsetsState.OnTraverseCallbacks mStartResizingAnimationIfNeeded =
+ new InsetsState.OnTraverseCallbacks() {
+
+ private @InsetsType int mTypes;
+ private InsetsState mToState;
+
+ @Override
+ public void onStart(InsetsState state1, InsetsState state2) {
+ mTypes = 0;
+ mToState = null;
+ }
+
+ @Override
+ public void onIdMatch(InsetsSource source1, InsetsSource source2) {
+ final @InsetsType int type = source1.getType();
+ if ((type & Type.systemBars()) == 0
+ || !source1.isVisible() || !source2.isVisible()
+ || source1.getFrame().equals(source2.getFrame())
+ || !(Rect.intersects(mFrame, source1.getFrame())
+ || Rect.intersects(mFrame, source2.getFrame()))) {
+ return;
+ }
+ mTypes |= type;
+ if (mToState == null) {
+ mToState = new InsetsState();
+ }
+ mToState.addSource(new InsetsSource(source2));
+ }
+
+ @Override
+ public void onFinish(InsetsState state1, InsetsState state2) {
+ if (mTypes == 0) {
+ return;
+ }
+ cancelExistingControllers(mTypes);
+ final InsetsAnimationControlRunner runner = new InsetsResizeAnimationRunner(
+ mFrame, state1, mToState, RESIZE_INTERPOLATOR,
+ ANIMATION_DURATION_RESIZE, mTypes, InsetsController.this);
+ mRunningAnimations.add(new RunningAnimation(runner, runner.getAnimationType()));
+ }
+ };
+
public InsetsController(Host host) {
this(host, (controller, source) -> {
if (source.getType() == ime()) {
@@ -671,7 +746,7 @@
WindowInsets insets = state.calculateInsets(mFrame, mState /* ignoringVisibilityState*/,
mLastInsets.isRound(), mLastInsets.shouldAlwaysConsumeSystemBars(),
mLastLegacySoftInputMode, mLastLegacyWindowFlags, mLastLegacySystemUiFlags,
- mWindowType, mLastWindowingMode, null /* typeSideMap */);
+ mWindowType, mLastWindowingMode, null /* idSideMap */);
mHost.dispatchWindowInsetsAnimationProgress(insets,
Collections.unmodifiableList(runningAnimations));
if (DEBUG) {
@@ -687,7 +762,7 @@
};
// Make mImeSourceConsumer always non-null.
- mImeSourceConsumer = getSourceConsumer(new InsetsSource(ITYPE_IME, ime()));
+ mImeSourceConsumer = getSourceConsumer(new InsetsSource(ID_IME, ime()));
}
@VisibleForTesting
@@ -739,29 +814,21 @@
true /* excludeInvisibleIme */)) {
if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged");
mHost.notifyInsetsChanged();
- startResizingAnimationIfNeeded(lastState);
+ if (lastState.getDisplayFrame().equals(mState.getDisplayFrame())) {
+ InsetsState.traverse(lastState, mState, mStartResizingAnimationIfNeeded);
+ }
}
return true;
}
private void updateState(InsetsState newState) {
mState.set(newState, 0 /* types */);
- for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
- final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
- final InsetsSource source = newState.peekSource(consumer.getId());
- if (source == null && consumer != mImeSourceConsumer) {
- // IME source consumer should always be there since we need to communicate with
- // InputMethodManager no matter we have the source or not.
- mSourceConsumers.removeAt(i);
- }
- }
@InsetsType int existingTypes = 0;
@InsetsType int visibleTypes = 0;
@InsetsType int disabledUserAnimationTypes = 0;
@InsetsType int[] cancelledUserAnimationTypes = {0};
- for (int i = 0; i < InsetsState.SIZE; i++) {
- InsetsSource source = newState.peekSource(i);
- if (source == null) continue;
+ for (int i = 0, size = newState.sourceSize(); i < size; i++) {
+ final InsetsSource source = newState.sourceAt(i);
@InsetsType int type = source.getType();
@AnimationType int animationType = getAnimationType(type);
if (!source.isUserControllable()) {
@@ -789,15 +856,7 @@
}
mVisibleTypes = visibleTypes;
}
- for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
- // Only update the server side insets here.
- if (!CAPTION_ON_SHELL && type == ITYPE_CAPTION_BAR) continue;
- InsetsSource source = mState.peekSource(type);
- if (source == null) continue;
- if (newState.peekSource(type) == null) {
- mState.removeSource(type);
- }
- }
+ InsetsState.traverse(mState, newState, mRemoveGoneSources);
updateDisabledUserAnimationTypes(disabledUserAnimationTypes);
@@ -825,52 +884,17 @@
if (CAPTION_ON_SHELL) {
return false;
}
- if (mState.peekSource(ITYPE_CAPTION_BAR) == null
- && mCaptionInsetsHeight == 0) {
+ final InsetsSource source = mState.peekSource(ID_CAPTION_BAR);
+ if (source == null && mCaptionInsetsHeight == 0) {
return false;
}
- if (mState.peekSource(ITYPE_CAPTION_BAR) != null
- && mCaptionInsetsHeight
- == mState.peekSource(ITYPE_CAPTION_BAR).getFrame().height()) {
+ if (source != null && mCaptionInsetsHeight == source.getFrame().height()) {
return false;
}
return true;
}
- private void startResizingAnimationIfNeeded(InsetsState fromState) {
- if (!fromState.getDisplayFrame().equals(mState.getDisplayFrame())) {
- return;
- }
- @InsetsType int types = 0;
- InsetsState toState = null;
- final ArraySet<Integer> internalTypes = InsetsState.toInternalType(Type.systemBars());
- for (int i = internalTypes.size() - 1; i >= 0; i--) {
- final @InternalInsetsType int type = internalTypes.valueAt(i);
- final InsetsSource fromSource = fromState.peekSource(type);
- final InsetsSource toSource = mState.peekSource(type);
- if (fromSource != null && toSource != null
- && fromSource.isVisible() && toSource.isVisible()
- && !fromSource.getFrame().equals(toSource.getFrame())
- && (Rect.intersects(mFrame, fromSource.getFrame())
- || Rect.intersects(mFrame, toSource.getFrame()))) {
- types |= toSource.getType();
- if (toState == null) {
- toState = new InsetsState();
- }
- toState.addSource(new InsetsSource(toSource));
- }
- }
- if (types == 0) {
- return;
- }
- cancelExistingControllers(types);
- final InsetsAnimationControlRunner runner = new InsetsResizeAnimationRunner(
- mFrame, fromState, toState, RESIZE_INTERPOLATOR, ANIMATION_DURATION_RESIZE, types,
- this);
- mRunningAnimations.add(new RunningAnimation(runner, runner.getAnimationType()));
- }
-
/**
* @see InsetsState#calculateInsets(Rect, InsetsState, boolean, boolean, int, int, int, int,
* int, SparseIntArray)
@@ -886,7 +910,7 @@
mLastLegacySystemUiFlags = legacySystemUiFlags;
mLastInsets = mState.calculateInsets(mFrame, null /* ignoringVisibilityState*/,
isScreenRound, alwaysConsumeSystemBars, legacySoftInputMode, legacyWindowFlags,
- legacySystemUiFlags, windowType, windowingMode, null /* typeSideMap */);
+ legacySystemUiFlags, windowType, windowingMode, null /* idSideMap */);
return mLastInsets;
}
@@ -1217,7 +1241,8 @@
ImeTracker.get().onFailed(statsToken,
ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
- if (fromIme && !mState.getSource(mImeSourceConsumer.getId()).isVisible()) {
+ if (fromIme
+ && !mState.isSourceOrDefaultVisible(mImeSourceConsumer.getId(), ime())) {
// We've requested IMM to show IME, but the IME is not controllable. We need to
// cancel the request.
setRequestedVisibleTypes(0 /* visibleTypes */, ime());
@@ -1757,10 +1782,10 @@
if (mCaptionInsetsHeight != height) {
mCaptionInsetsHeight = height;
if (mCaptionInsetsHeight != 0) {
- mState.getSource(ITYPE_CAPTION_BAR).setFrame(mFrame.left, mFrame.top,
- mFrame.right, mFrame.top + mCaptionInsetsHeight);
+ mState.getOrCreateSource(ID_CAPTION_BAR, captionBar()).setFrame(
+ mFrame.left, mFrame.top, mFrame.right, mFrame.top + mCaptionInsetsHeight);
} else {
- mState.removeSource(ITYPE_CAPTION_BAR);
+ mState.removeSource(ID_CAPTION_BAR);
}
mHost.notifyInsetsChanged();
}
diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java
index cf64eedf..bffaeea 100644
--- a/core/java/android/view/InsetsResizeAnimationRunner.java
+++ b/core/java/android/view/InsetsResizeAnimationRunner.java
@@ -33,7 +33,6 @@
import android.graphics.Rect;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
-import android.view.InsetsState.InternalInsetsType;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation.Bounds;
import android.view.animation.Interpolator;
@@ -142,24 +141,23 @@
return false;
}
final float fraction = mAnimation.getInterpolatedFraction();
- for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
- final InsetsSource fromSource = mFromState.peekSource(type);
- final InsetsSource toSource = mToState.peekSource(type);
- if (fromSource == null || toSource == null) {
- continue;
+ InsetsState.traverse(mFromState, mToState, new InsetsState.OnTraverseCallbacks() {
+ @Override
+ public void onIdMatch(InsetsSource fromSource, InsetsSource toSource) {
+ final Rect fromFrame = fromSource.getFrame();
+ final Rect toFrame = toSource.getFrame();
+ final Rect frame = new Rect(
+ (int) (fromFrame.left + fraction * (toFrame.left - fromFrame.left)),
+ (int) (fromFrame.top + fraction * (toFrame.top - fromFrame.top)),
+ (int) (fromFrame.right + fraction * (toFrame.right - fromFrame.right)),
+ (int) (fromFrame.bottom + fraction * (toFrame.bottom - fromFrame.bottom)));
+ final InsetsSource source =
+ new InsetsSource(fromSource.getId(), fromSource.getType());
+ source.setFrame(frame);
+ source.setVisible(toSource.isVisible());
+ outState.addSource(source);
}
- final Rect fromFrame = fromSource.getFrame();
- final Rect toFrame = toSource.getFrame();
- final Rect frame = new Rect(
- (int) (fromFrame.left + fraction * (toFrame.left - fromFrame.left)),
- (int) (fromFrame.top + fraction * (toFrame.top - fromFrame.top)),
- (int) (fromFrame.right + fraction * (toFrame.right - fromFrame.right)),
- (int) (fromFrame.bottom + fraction * (toFrame.bottom - fromFrame.bottom)));
- final InsetsSource source = new InsetsSource(type, fromSource.getType());
- source.setFrame(frame);
- source.setVisible(toSource.isVisible());
- outState.addSource(source);
- }
+ });
if (mFinished) {
mController.notifyFinished(this, true /* shown */);
}
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index f6b063d..17ab83c 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -21,7 +21,9 @@
import static android.view.InsetsSourceProto.VISIBLE;
import static android.view.InsetsSourceProto.VISIBLE_FRAME;
import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
+import static android.view.WindowInsets.Type.ime;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Insets;
@@ -40,6 +42,9 @@
*/
public class InsetsSource implements Parcelable {
+ /** The insets source ID of IME */
+ public static final int ID_IME = createId(null, 0, ime());
+
/**
* An unique integer to identify this source across processes.
*/
@@ -83,20 +88,24 @@
mInsetsRoundedCornerFrame = other.mInsetsRoundedCornerFrame;
}
- public void setFrame(int left, int top, int right, int bottom) {
+ public InsetsSource setFrame(int left, int top, int right, int bottom) {
mFrame.set(left, top, right, bottom);
+ return this;
}
- public void setFrame(Rect frame) {
+ public InsetsSource setFrame(Rect frame) {
mFrame.set(frame);
+ return this;
}
- public void setVisibleFrame(@Nullable Rect visibleFrame) {
+ public InsetsSource setVisibleFrame(@Nullable Rect visibleFrame) {
mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : null;
+ return this;
}
- public void setVisible(boolean visible) {
+ public InsetsSource setVisible(boolean visible) {
mVisible = visible;
+ return this;
}
public int getId() {
@@ -128,8 +137,9 @@
return mInsetsRoundedCornerFrame;
}
- public void setInsetsRoundedCornerFrame(boolean insetsRoundedCornerFrame) {
+ public InsetsSource setInsetsRoundedCornerFrame(boolean insetsRoundedCornerFrame) {
mInsetsRoundedCornerFrame = insetsRoundedCornerFrame;
+ return this;
}
/**
@@ -223,6 +233,29 @@
}
/**
+ * Creates an identifier of an {@link InsetsSource}.
+ *
+ * @param owner An object owned by the owner. Only the owner can modify its own sources.
+ * @param index An owner may have multiple sources with the same type. For example, the system
+ * server might have multiple display cutout sources. This is used to identify
+ * which one is which. The value must be in a range of [0, 2047].
+ * @param type The {@link WindowInsets.Type.InsetsType type} of the source.
+ * @return a unique integer as the identifier.
+ */
+ public static int createId(Object owner, @IntRange(from = 0, to = 2047) int index,
+ @InsetsType int type) {
+ if (index < 0 || index >= 2048) {
+ throw new IllegalArgumentException();
+ }
+ // owner takes top 16 bits;
+ // index takes 11 bits since the 6th bit;
+ // type takes bottom 5 bits.
+ return (((owner != null ? owner.hashCode() : 1) % (1 << 16)) << 16)
+ + (index << 5)
+ + WindowInsets.Type.indexOf(type);
+ }
+
+ /**
* Export the state of {@link InsetsSource} into a protocol buffer output stream.
*
* @param proto Stream to write the state to
@@ -241,7 +274,7 @@
public void dump(String prefix, PrintWriter pw) {
pw.print(prefix);
- pw.print("InsetsSource id="); pw.print(mId);
+ pw.print("InsetsSource id="); pw.print(Integer.toHexString(mId));
pw.print(" type="); pw.print(WindowInsets.Type.toString(mType));
pw.print(" frame="); pw.print(mFrame.toShortString());
if (mVisibleFrame != null) {
@@ -258,7 +291,7 @@
}
/**
- * @param excludeInvisibleImeFrames If {@link InsetsState#ITYPE_IME} frames should be ignored
+ * @param excludeInvisibleImeFrames If {@link WindowInsets.Type#ime()} frames should be ignored
* when IME is not visible.
*/
public boolean equals(@Nullable Object o, boolean excludeInvisibleImeFrames) {
@@ -316,8 +349,7 @@
@Override
public String toString() {
- return "InsetsSource: {"
- + "mId=" + mId
+ return "InsetsSource: {" + Integer.toHexString(mId)
+ " mType=" + WindowInsets.Type.toString(mType)
+ " mFrame=" + mFrame.toShortString()
+ " mVisible=" + mVisible
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index c849cb5..7ea93f5 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -205,8 +205,7 @@
@Override
public String toString() {
- return "InsetsSourceControl: {"
- + "mId=" + mId
+ return "InsetsSourceControl: {" + Integer.toHexString(mId)
+ " mType=" + WindowInsets.Type.toString(mType)
+ (mInitiallyVisible ? " initiallyVisible" : "")
+ " mSurfacePosition=" + mSurfacePosition
@@ -217,7 +216,7 @@
public void dump(String prefix, PrintWriter pw) {
pw.print(prefix);
- pw.print("InsetsSourceControl mId="); pw.print(mId);
+ pw.print("InsetsSourceControl mId="); pw.print(Integer.toHexString(mId));
pw.print(" mType="); pw.print(WindowInsets.Type.toString(mType));
pw.print(" mLeash="); pw.print(mLeash);
pw.print(" mInitiallyVisible="); pw.print(mInitiallyVisible);
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 054d177..70a7739 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -20,6 +20,7 @@
import static android.view.InsetsStateProto.DISPLAY_FRAME;
import static android.view.InsetsStateProto.SOURCES;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
+import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.indexOf;
@@ -42,6 +43,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
+import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
import android.view.WindowInsets.Type;
@@ -53,7 +55,6 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
import java.util.Objects;
import java.util.StringJoiner;
@@ -85,11 +86,6 @@
ITYPE_TOP_TAPPABLE_ELEMENT,
ITYPE_RIGHT_TAPPABLE_ELEMENT,
ITYPE_BOTTOM_TAPPABLE_ELEMENT,
- ITYPE_LEFT_DISPLAY_CUTOUT,
- ITYPE_TOP_DISPLAY_CUTOUT,
- ITYPE_RIGHT_DISPLAY_CUTOUT,
- ITYPE_BOTTOM_DISPLAY_CUTOUT,
- ITYPE_IME,
ITYPE_CLIMATE_BAR,
ITYPE_EXTRA_NAVIGATION_BAR,
ITYPE_LEFT_GENERIC_OVERLAY,
@@ -99,15 +95,7 @@
})
public @interface InternalInsetsType {}
- /**
- * Special value to be used to by methods returning an {@link InternalInsetsType} to indicate
- * that the objects/parameters aren't associated with an {@link InternalInsetsType}
- */
- public static final int ITYPE_INVALID = -1;
-
- static final int FIRST_TYPE = 0;
-
- public static final int ITYPE_STATUS_BAR = FIRST_TYPE;
+ public static final int ITYPE_STATUS_BAR = 0;
public static final int ITYPE_NAVIGATION_BAR = 1;
public static final int ITYPE_CAPTION_BAR = 2;
@@ -121,19 +109,11 @@
public static final int ITYPE_LEFT_MANDATORY_GESTURES = 9;
public static final int ITYPE_RIGHT_MANDATORY_GESTURES = 10;
- public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 11;
- public static final int ITYPE_TOP_DISPLAY_CUTOUT = 12;
- public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 13;
- public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 14;
-
public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = 15;
public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 16;
public static final int ITYPE_RIGHT_TAPPABLE_ELEMENT = 17;
public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 18;
- /** Input method window. */
- public static final int ITYPE_IME = 19;
-
/** Additional system decorations inset type. */
public static final int ITYPE_CLIMATE_BAR = 20;
public static final int ITYPE_EXTRA_NAVIGATION_BAR = 21;
@@ -144,16 +124,8 @@
public static final int ITYPE_RIGHT_GENERIC_OVERLAY = 24;
public static final int ITYPE_BOTTOM_GENERIC_OVERLAY = 25;
- static final int LAST_TYPE = ITYPE_BOTTOM_GENERIC_OVERLAY;
- public static final int SIZE = LAST_TYPE + 1;
-
- // Derived types
-
- /** A shelf is the same as the navigation bar. */
- public static final int ITYPE_SHELF = ITYPE_NAVIGATION_BAR;
-
@Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = "IINSETS_SIDE", value = {
+ @IntDef(prefix = "ISIDE", value = {
ISIDE_LEFT,
ISIDE_TOP,
ISIDE_RIGHT,
@@ -169,7 +141,7 @@
static final int ISIDE_FLOATING = 4;
static final int ISIDE_UNKNOWN = 5;
- private final InsetsSource[] mSources = new InsetsSource[SIZE];
+ private final SparseArray<InsetsSource> mSources;
/**
* The frame of the display these sources are relative to.
@@ -201,13 +173,15 @@
private DisplayShape mDisplayShape = DisplayShape.NONE;
public InsetsState() {
+ mSources = new SparseArray<>();
}
public InsetsState(InsetsState copy) {
- set(copy);
+ this(copy, false /* copySources */);
}
public InsetsState(InsetsState copy, boolean copySources) {
+ mSources = new SparseArray<>(copy.mSources.size());
set(copy, copySources);
}
@@ -224,36 +198,29 @@
boolean isScreenRound, boolean alwaysConsumeSystemBars,
int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags,
int windowType, @WindowConfiguration.WindowingMode int windowingMode,
- @Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
+ @Nullable @InternalInsetsSide SparseIntArray idSideMap) {
Insets[] typeInsetsMap = new Insets[Type.SIZE];
Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
boolean[] typeVisibilityMap = new boolean[Type.SIZE];
final Rect relativeFrame = new Rect(frame);
final Rect relativeFrameMax = new Rect(frame);
- for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
- InsetsSource source = mSources[type];
- if (source == null) {
- int index = indexOf(toPublicType(type));
- if (typeInsetsMap[index] == null) {
- typeInsetsMap[index] = Insets.NONE;
- }
- continue;
- }
+ for (int i = mSources.size() - 1; i >= 0; i--) {
+ final InsetsSource source = mSources.valueAt(i);
processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap,
- typeSideMap, typeVisibilityMap);
+ idSideMap, typeVisibilityMap);
// IME won't be reported in max insets as the size depends on the EditorInfo of the IME
// target.
if (source.getType() != WindowInsets.Type.ime()) {
InsetsSource ignoringVisibilitySource = ignoringVisibilityState != null
- ? ignoringVisibilityState.getSource(type)
+ ? ignoringVisibilityState.peekSource(source.getId())
: source;
if (ignoringVisibilitySource == null) {
continue;
}
processSource(ignoringVisibilitySource, relativeFrameMax,
- true /* ignoreVisibility */, typeMaxInsetsMap, null /* typeSideMap */,
+ true /* ignoreVisibility */, typeMaxInsetsMap, null /* idSideMap */,
null /* typeVisibilityMap */);
}
}
@@ -306,8 +273,9 @@
// If mRoundedCornerFrame is set, we should calculate the new RoundedCorners based on this
// frame.
final Rect roundedCornerFrame = new Rect(mRoundedCornerFrame);
- for (InsetsSource source : mSources) {
- if (source != null && source.getInsetsRoundedCornerFrame()) {
+ for (int i = mSources.size() - 1; i >= 0; i--) {
+ final InsetsSource source = mSources.valueAt(i);
+ if (source.getInsetsRoundedCornerFrame()) {
final Insets insets = source.calculateInsets(roundedCornerFrame, false);
roundedCornerFrame.inset(insets);
}
@@ -351,13 +319,9 @@
public Insets calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility) {
Insets insets = Insets.NONE;
- for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
- InsetsSource source = mSources[type];
- if (source == null) {
- continue;
- }
- int publicType = InsetsState.toPublicType(type);
- if ((publicType & types) == 0) {
+ for (int i = mSources.size() - 1; i >= 0; i--) {
+ final InsetsSource source = mSources.valueAt(i);
+ if ((source.getType() & types) == 0) {
continue;
}
insets = Insets.max(source.calculateInsets(frame, ignoreVisibility), insets);
@@ -368,13 +332,9 @@
public Insets calculateInsets(Rect frame, @InsetsType int types,
@InsetsType int requestedVisibleTypes) {
Insets insets = Insets.NONE;
- for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
- InsetsSource source = mSources[type];
- if (source == null) {
- continue;
- }
- int publicType = InsetsState.toPublicType(type);
- if ((publicType & types & requestedVisibleTypes) == 0) {
+ for (int i = mSources.size() - 1; i >= 0; i--) {
+ final InsetsSource source = mSources.valueAt(i);
+ if ((source.getType() & types & requestedVisibleTypes) == 0) {
continue;
}
insets = Insets.max(source.calculateInsets(frame, true), insets);
@@ -392,13 +352,9 @@
? systemBars() | ime()
: systemBars();
Insets insets = Insets.NONE;
- for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
- InsetsSource source = mSources[type];
- if (source == null) {
- continue;
- }
- final int publicType = InsetsState.toPublicType(type);
- if ((publicType & visibleInsetsTypes) == 0) {
+ for (int i = mSources.size() - 1; i >= 0; i--) {
+ final InsetsSource source = mSources.valueAt(i);
+ if ((source.getType() & visibleInsetsTypes) == 0) {
continue;
}
insets = Insets.max(source.calculateVisibleInsets(frame), insets);
@@ -416,13 +372,10 @@
@InsetsType
public int calculateUncontrollableInsetsFromFrame(Rect frame) {
int blocked = 0;
- for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
- InsetsSource source = mSources[type];
- if (source == null) {
- continue;
- }
+ for (int i = mSources.size() - 1; i >= 0; i--) {
+ final InsetsSource source = mSources.valueAt(i);
if (!canControlSource(frame, source)) {
- blocked |= toPublicType(type);
+ blocked |= source.getType();
}
}
return blocked;
@@ -438,12 +391,12 @@
}
private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
- Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray typeSideMap,
+ Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray idSideMap,
@Nullable boolean[] typeVisibilityMap) {
Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility);
final int type = source.getType();
- processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
+ processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
insets, type);
if (type == Type.MANDATORY_SYSTEM_GESTURES) {
@@ -452,24 +405,24 @@
// Type.systemGestureInsets() as NORMAL | MANDATORY, but then we lose the
// ability to set systemGestureInsets() independently from
// mandatorySystemGestureInsets() in the Builder.
- processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
+ processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
insets, Type.SYSTEM_GESTURES);
}
if (type == Type.CAPTION_BAR) {
// Caption should also be gesture and tappable elements. This should not be needed when
// the caption is added from the shell, as the shell can add other types at the same
// time.
- processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
+ processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
insets, Type.SYSTEM_GESTURES);
- processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
+ processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
insets, Type.MANDATORY_SYSTEM_GESTURES);
- processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
+ processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
insets, Type.TAPPABLE_ELEMENT);
}
}
private void processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap,
- @InternalInsetsSide @Nullable SparseIntArray typeSideMap,
+ @InternalInsetsSide @Nullable SparseIntArray idSideMap,
@Nullable boolean[] typeVisibilityMap, Insets insets, int type) {
int index = indexOf(type);
Insets existing = typeInsetsMap[index];
@@ -483,10 +436,10 @@
typeVisibilityMap[index] = source.isVisible();
}
- if (typeSideMap != null) {
+ if (idSideMap != null) {
@InternalInsetsSide int insetSide = getInsetSide(insets);
if (insetSide != ISIDE_UNKNOWN) {
- typeSideMap.put(source.getId(), insetSide);
+ idSideMap.put(source.getId(), insetSide);
}
}
}
@@ -514,31 +467,60 @@
return ISIDE_UNKNOWN;
}
- public InsetsSource getSource(@InternalInsetsType int type) {
- InsetsSource source = mSources[type];
+ /**
+ * Gets the source mapped from the ID, or creates one if no such mapping has been made.
+ */
+ public InsetsSource getOrCreateSource(int id, int type) {
+ InsetsSource source = mSources.get(id);
if (source != null) {
return source;
}
- source = new InsetsSource(type, toPublicType(type));
- mSources[type] = source;
+ source = new InsetsSource(id, type);
+ mSources.put(id, source);
return source;
}
- public @Nullable InsetsSource peekSource(@InternalInsetsType int type) {
- return mSources[type];
+ /**
+ * Gets the source mapped from the ID, or <code>null</code> if no such mapping has been made.
+ */
+ public @Nullable InsetsSource peekSource(int id) {
+ return mSources.get(id);
}
/**
- * Returns the source visibility or the default visibility if the source doesn't exist. This is
- * useful if when treating this object as a request.
+ * Given an index in the range <code>0...sourceSize()-1</code>, returns the source ID from the
+ * <code>index</code>th ID-source mapping that this state stores.
+ */
+ public int sourceIdAt(int index) {
+ return mSources.keyAt(index);
+ }
+
+ /**
+ * Given an index in the range <code>0...sourceSize()-1</code>, returns the source from the
+ * <code>index</code>th ID-source mapping that this state stores.
+ */
+ public InsetsSource sourceAt(int index) {
+ return mSources.valueAt(index);
+ }
+
+ /**
+ * Returns the amount of the sources.
+ */
+ public int sourceSize() {
+ return mSources.size();
+ }
+
+ /**
+ * Returns if the source is visible or the type is default visible and the source doesn't exist.
*
- * @param type The {@link InternalInsetsType} to query.
+ * @param id The ID of the source.
+ * @param type The {@link InsetsType} to see if it is default visible.
* @return {@code true} if the source is visible or the type is default visible and the source
* doesn't exist.
*/
- public boolean getSourceOrDefaultVisibility(@InternalInsetsType int type) {
- final InsetsSource source = mSources[type];
- return source != null ? source.isVisible() : getDefaultVisibility(type);
+ public boolean isSourceOrDefaultVisible(int id, @InsetsType int type) {
+ final InsetsSource source = mSources.get(id);
+ return source != null ? source.isVisible() : (type & Type.defaultVisible()) != 0;
}
public void setDisplayFrame(Rect frame) {
@@ -612,28 +594,31 @@
}
/**
- * Modifies the state of this class to exclude a certain type to make it ready for dispatching
- * to the client.
+ * Removes the source which has the ID from this state, if there was any.
*
- * @param type The {@link InternalInsetsType} of the source to remove
- * @return {@code true} if this InsetsState was modified; {@code false} otherwise.
+ * @param id The ID of the source to remove.
*/
- public boolean removeSource(@InternalInsetsType int type) {
- if (mSources[type] == null) {
- return false;
- }
- mSources[type] = null;
- return true;
+ public void removeSource(int id) {
+ mSources.delete(id);
+ }
+
+ /**
+ * Removes the source at the specified index.
+ *
+ * @param index The index of the source to remove.
+ */
+ public void removeSourceAt(int index) {
+ mSources.removeAt(index);
}
/**
* A shortcut for setting the visibility of the source.
*
- * @param type The {@link InternalInsetsType} of the source to set the visibility
+ * @param id The ID of the source to set the visibility
* @param visible {@code true} for visible
*/
- public void setSourceVisible(@InternalInsetsType int type, boolean visible) {
- InsetsSource source = mSources[type];
+ public void setSourceVisible(int id, boolean visible) {
+ final InsetsSource source = mSources.get(id);
if (source != null) {
source.setVisible(visible);
}
@@ -651,14 +636,12 @@
mRoundedCornerFrame.scale(scale);
mPrivacyIndicatorBounds = mPrivacyIndicatorBounds.scale(scale);
mDisplayShape = mDisplayShape.setScale(scale);
- for (int i = 0; i < SIZE; i++) {
- final InsetsSource source = mSources[i];
- if (source != null) {
- source.getFrame().scale(scale);
- final Rect visibleFrame = source.getVisibleFrame();
- if (visibleFrame != null) {
- visibleFrame.scale(scale);
- }
+ for (int i = mSources.size() - 1; i >= 0; i--) {
+ final InsetsSource source = mSources.valueAt(i);
+ source.getFrame().scale(scale);
+ final Rect visibleFrame = source.getVisibleFrame();
+ if (visibleFrame != null) {
+ visibleFrame.scale(scale);
}
}
}
@@ -674,15 +657,12 @@
mRoundedCornerFrame.set(other.mRoundedCornerFrame);
mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds();
mDisplayShape = other.getDisplayShape();
- if (copySources) {
- for (int i = 0; i < SIZE; i++) {
- InsetsSource source = other.mSources[i];
- mSources[i] = source != null ? new InsetsSource(source) : null;
- }
- } else {
- for (int i = 0; i < SIZE; i++) {
- mSources[i] = other.mSources[i];
- }
+ mSources.clear();
+ for (int i = 0, size = other.mSources.size(); i < size; i++) {
+ final InsetsSource otherSource = other.mSources.valueAt(i);
+ mSources.append(otherSource.getId(), copySources
+ ? new InsetsSource(otherSource)
+ : otherSource);
}
}
@@ -700,15 +680,25 @@
mRoundedCornerFrame.set(other.mRoundedCornerFrame);
mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds();
mDisplayShape = other.getDisplayShape();
- final ArraySet<Integer> t = toInternalType(types);
- for (int i = t.size() - 1; i >= 0; i--) {
- final int type = t.valueAt(i);
- mSources[type] = other.mSources[type];
+ if (types == 0) {
+ return;
+ }
+ for (int i = mSources.size() - 1; i >= 0; i--) {
+ final InsetsSource source = mSources.valueAt(i);
+ if ((source.getType() & types) != 0) {
+ mSources.removeAt(i);
+ }
+ }
+ for (int i = other.mSources.size() - 1; i >= 0; i--) {
+ final InsetsSource otherSource = other.mSources.valueAt(i);
+ if ((otherSource.getType() & types) != 0) {
+ mSources.put(otherSource.getId(), otherSource);
+ }
}
}
public void addSource(InsetsSource source) {
- mSources[source.getId()] = source;
+ mSources.put(source.getId(), source);
}
public static boolean clearsCompatInsets(int windowType, int windowFlags, int windowingMode) {
@@ -748,15 +738,6 @@
result.add(ITYPE_RIGHT_MANDATORY_GESTURES);
result.add(ITYPE_BOTTOM_MANDATORY_GESTURES);
}
- if ((types & Type.DISPLAY_CUTOUT) != 0) {
- result.add(ITYPE_LEFT_DISPLAY_CUTOUT);
- result.add(ITYPE_TOP_DISPLAY_CUTOUT);
- result.add(ITYPE_RIGHT_DISPLAY_CUTOUT);
- result.add(ITYPE_BOTTOM_DISPLAY_CUTOUT);
- }
- if ((types & Type.IME) != 0) {
- result.add(ITYPE_IME);
- }
return result;
}
@@ -780,8 +761,6 @@
return Type.SYSTEM_OVERLAYS;
case ITYPE_CAPTION_BAR:
return Type.CAPTION_BAR;
- case ITYPE_IME:
- return Type.IME;
case ITYPE_TOP_MANDATORY_GESTURES:
case ITYPE_BOTTOM_MANDATORY_GESTURES:
case ITYPE_LEFT_MANDATORY_GESTURES:
@@ -797,20 +776,11 @@
case ITYPE_RIGHT_TAPPABLE_ELEMENT:
case ITYPE_BOTTOM_TAPPABLE_ELEMENT:
return Type.TAPPABLE_ELEMENT;
- case ITYPE_LEFT_DISPLAY_CUTOUT:
- case ITYPE_TOP_DISPLAY_CUTOUT:
- case ITYPE_RIGHT_DISPLAY_CUTOUT:
- case ITYPE_BOTTOM_DISPLAY_CUTOUT:
- return Type.DISPLAY_CUTOUT;
default:
throw new IllegalArgumentException("Unknown type: " + type);
}
}
- public static boolean getDefaultVisibility(@InternalInsetsType int type) {
- return type != ITYPE_IME;
- }
-
public static boolean containsType(@InternalInsetsType int[] types,
@InternalInsetsType int type) {
if (types == null) {
@@ -833,16 +803,14 @@
pw.println(newPrefix + "mRoundedCornerFrame=" + mRoundedCornerFrame);
pw.println(newPrefix + "mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds);
pw.println(newPrefix + "mDisplayShape=" + mDisplayShape);
- for (int i = 0; i < SIZE; i++) {
- InsetsSource source = mSources[i];
- if (source == null) continue;
- source.dump(newPrefix + " ", pw);
+ for (int i = 0, size = mSources.size(); i < size; i++) {
+ mSources.valueAt(i).dump(newPrefix + " ", pw);
}
}
void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- InsetsSource source = mSources[ITYPE_IME];
+ final InsetsSource source = mSources.get(InsetsSource.ID_IME);
if (source != null) {
source.dumpDebug(proto, SOURCES);
}
@@ -883,16 +851,6 @@
return "ITYPE_RIGHT_TAPPABLE_ELEMENT";
case ITYPE_BOTTOM_TAPPABLE_ELEMENT:
return "ITYPE_BOTTOM_TAPPABLE_ELEMENT";
- case ITYPE_LEFT_DISPLAY_CUTOUT:
- return "ITYPE_LEFT_DISPLAY_CUTOUT";
- case ITYPE_TOP_DISPLAY_CUTOUT:
- return "ITYPE_TOP_DISPLAY_CUTOUT";
- case ITYPE_RIGHT_DISPLAY_CUTOUT:
- return "ITYPE_RIGHT_DISPLAY_CUTOUT";
- case ITYPE_BOTTOM_DISPLAY_CUTOUT:
- return "ITYPE_BOTTOM_DISPLAY_CUTOUT";
- case ITYPE_IME:
- return "ITYPE_IME";
case ITYPE_CLIMATE_BAR:
return "ITYPE_CLIMATE_BAR";
case ITYPE_EXTRA_NAVIGATION_BAR:
@@ -921,8 +879,8 @@
* excluded.
* @param excludingCaptionInsets {@code true} if we want to compare two InsetsState objects but
* ignore the caption insets source value.
- * @param excludeInvisibleImeFrames If {@link #ITYPE_IME} frames should be ignored when IME is
- * not visible.
+ * @param excludeInvisibleImeFrames If {@link WindowInsets.Type#ime()} frames should be ignored
+ * when IME is not visible.
* @return {@code true} if the two InsetsState objects are equal, {@code false} otherwise.
*/
@VisibleForTesting
@@ -941,38 +899,56 @@
|| !mDisplayShape.equals(state.mDisplayShape)) {
return false;
}
- for (int i = 0; i < SIZE; i++) {
- if (excludingCaptionInsets) {
- if (i == ITYPE_CAPTION_BAR) continue;
+
+ final SparseArray<InsetsSource> thisSources = mSources;
+ final SparseArray<InsetsSource> thatSources = state.mSources;
+ if (!excludingCaptionInsets && !excludeInvisibleImeFrames) {
+ return thisSources.contentEquals(thatSources);
+ } else {
+ final int thisSize = thisSources.size();
+ final int thatSize = thatSources.size();
+ int thisIndex = 0;
+ int thatIndex = 0;
+ while (thisIndex < thisSize && thatIndex < thatSize) {
+ // Seek to the next non-excluding source of ours.
+ InsetsSource thisSource = thisSources.valueAt(thisIndex);
+ while (thisSource != null
+ && (excludingCaptionInsets && thisSource.getType() == captionBar()
+ || excludeInvisibleImeFrames && thisSource.getType() == ime()
+ && !thisSource.isVisible())) {
+ thisIndex++;
+ thisSource = thisIndex < thisSize ? thisSources.valueAt(thisIndex) : null;
+ }
+
+ // Seek to the next non-excluding source of theirs.
+ InsetsSource thatSource = thatSources.valueAt(thatIndex);
+ while (thatSource != null
+ && (excludingCaptionInsets && thatSource.getType() == captionBar()
+ || excludeInvisibleImeFrames && thatSource.getType() == ime()
+ && !thatSource.isVisible())) {
+ thatIndex++;
+ thatSource = thatIndex < thatSize ? thatSources.valueAt(thatIndex) : null;
+ }
+
+ if (!Objects.equals(thisSource, thatSource)) {
+ return false;
+ }
+
+ thisIndex++;
+ thatIndex++;
}
- InsetsSource source = mSources[i];
- InsetsSource otherSource = state.mSources[i];
- if (source == null && otherSource == null) {
- continue;
- }
- if (excludeInvisibleImeFrames && i == ITYPE_IME
- && ((source == null && !otherSource.isVisible())
- || (otherSource == null && !source.isVisible()))) {
- continue;
- }
- if (source == null || otherSource == null) {
- return false;
- }
- if (!otherSource.equals(source, excludeInvisibleImeFrames)) {
- return false;
- }
+ return thisIndex >= thisSize && thatIndex >= thatSize;
}
- return true;
}
@Override
public int hashCode() {
- return Objects.hash(mDisplayFrame, mDisplayCutout, Arrays.hashCode(mSources),
+ return Objects.hash(mDisplayFrame, mDisplayCutout, mSources.contentHashCode(),
mRoundedCorners, mPrivacyIndicatorBounds, mRoundedCornerFrame, mDisplayShape);
}
public InsetsState(Parcel in) {
- readFromParcel(in);
+ mSources = readFromParcel(in);
}
@Override
@@ -984,14 +960,18 @@
public void writeToParcel(Parcel dest, int flags) {
mDisplayFrame.writeToParcel(dest, flags);
mDisplayCutout.writeToParcel(dest, flags);
- dest.writeTypedArray(mSources, 0 /* parcelableFlags */);
dest.writeTypedObject(mRoundedCorners, flags);
mRoundedCornerFrame.writeToParcel(dest, flags);
dest.writeTypedObject(mPrivacyIndicatorBounds, flags);
dest.writeTypedObject(mDisplayShape, flags);
+ final int size = mSources.size();
+ dest.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ dest.writeTypedObject(mSources.valueAt(i), flags);
+ }
}
- public static final @NonNull Creator<InsetsState> CREATOR = new Creator<InsetsState>() {
+ public static final @NonNull Creator<InsetsState> CREATOR = new Creator<>() {
public InsetsState createFromParcel(Parcel in) {
return new InsetsState(in);
@@ -1002,24 +982,34 @@
}
};
- public void readFromParcel(Parcel in) {
+ public SparseArray<InsetsSource> readFromParcel(Parcel in) {
mDisplayFrame.readFromParcel(in);
mDisplayCutout.readFromParcel(in);
- in.readTypedArray(mSources, InsetsSource.CREATOR);
mRoundedCorners = in.readTypedObject(RoundedCorners.CREATOR);
mRoundedCornerFrame.readFromParcel(in);
mPrivacyIndicatorBounds = in.readTypedObject(PrivacyIndicatorBounds.CREATOR);
mDisplayShape = in.readTypedObject(DisplayShape.CREATOR);
+ final int size = in.readInt();
+ final SparseArray<InsetsSource> sources;
+ if (mSources == null) {
+ // We are constructing this InsetsState.
+ sources = new SparseArray<>(size);
+ } else {
+ sources = mSources;
+ sources.clear();
+ }
+ for (int i = 0; i < size; i++) {
+ final InsetsSource source = in.readTypedObject(InsetsSource.CREATOR);
+ sources.append(source.getId(), source);
+ }
+ return sources;
}
@Override
public String toString() {
- StringJoiner joiner = new StringJoiner(", ");
- for (int i = 0; i < SIZE; i++) {
- InsetsSource source = mSources[i];
- if (source != null) {
- joiner.add(source.toString());
- }
+ final StringJoiner joiner = new StringJoiner(", ");
+ for (int i = 0, size = mSources.size(); i < size; i++) {
+ joiner.add(mSources.valueAt(i).toString());
}
return "InsetsState: {"
+ "mDisplayFrame=" + mDisplayFrame
@@ -1031,5 +1021,112 @@
+ ", mSources= { " + joiner
+ " }";
}
+
+ /**
+ * Traverses sources in two {@link InsetsState}s and calls back when events defined in
+ * {@link OnTraverseCallbacks} happen. This is optimized for {@link SparseArray} that we avoid
+ * triggering the binary search while getting the key or the value.
+ *
+ * This can be used to copy attributes of sources from one InsetsState to the other one, or to
+ * remove sources existing in one InsetsState but not in the other one.
+ *
+ * @param state1 The first {@link InsetsState} to be traversed.
+ * @param state2 The second {@link InsetsState} to be traversed.
+ * @param cb The {@link OnTraverseCallbacks} to call back to the caller.
+ */
+ public static void traverse(InsetsState state1, InsetsState state2, OnTraverseCallbacks cb) {
+ cb.onStart(state1, state2);
+ final int size1 = state1.sourceSize();
+ final int size2 = state2.sourceSize();
+ int index1 = 0;
+ int index2 = 0;
+ while (index1 < size1 && index2 < size2) {
+ int id1 = state1.sourceIdAt(index1);
+ int id2 = state2.sourceIdAt(index2);
+ while (id1 != id2) {
+ if (id1 < id2) {
+ cb.onIdNotFoundInState2(index1, state1.sourceAt(index1));
+ index1++;
+ if (index1 < size1) {
+ id1 = state1.sourceIdAt(index1);
+ } else {
+ break;
+ }
+ } else {
+ cb.onIdNotFoundInState1(index2, state2.sourceAt(index2));
+ index2++;
+ if (index2 < size2) {
+ id2 = state2.sourceIdAt(index2);
+ } else {
+ break;
+ }
+ }
+ }
+ if (index1 >= size1 || index2 >= size2) {
+ break;
+ }
+ final InsetsSource source1 = state1.sourceAt(index1);
+ final InsetsSource source2 = state2.sourceAt(index2);
+ cb.onIdMatch(source1, source2);
+ index1++;
+ index2++;
+ }
+ while (index2 < size2) {
+ cb.onIdNotFoundInState1(index2, state2.sourceAt(index2));
+ index2++;
+ }
+ while (index1 < size1) {
+ cb.onIdNotFoundInState2(index1, state1.sourceAt(index1));
+ index1++;
+ }
+ cb.onFinish(state1, state2);
+ }
+
+ /**
+ * Used with {@link #traverse(InsetsState, InsetsState, OnTraverseCallbacks)} to call back when
+ * certain events happen.
+ */
+ public interface OnTraverseCallbacks {
+
+ /**
+ * Called at the beginning of the traverse.
+ *
+ * @param state1 same as the state1 supplied to {@link #traverse}
+ * @param state2 same as the state2 supplied to {@link #traverse}
+ */
+ default void onStart(InsetsState state1, InsetsState state2) { }
+
+ /**
+ * Called when finding two IDs from two InsetsStates are the same.
+ *
+ * @param source1 the source in state1.
+ * @param source2 the source in state2.
+ */
+ default void onIdMatch(InsetsSource source1, InsetsSource source2) { }
+
+ /**
+ * Called when finding an ID in state2 but not in state1.
+ *
+ * @param index2 the index of the ID in state2.
+ * @param source2 the source which has the ID in state2.
+ */
+ default void onIdNotFoundInState1(int index2, InsetsSource source2) { }
+
+ /**
+ * Called when finding an ID in state1 but not in state2.
+ *
+ * @param index1 the index of the ID in state1.
+ * @param source1 the source which has the ID in state1.
+ */
+ default void onIdNotFoundInState2(int index1, InsetsSource source1) { }
+
+ /**
+ * Called at the end of the traverse.
+ *
+ * @param state1 same as the state1 supplied to {@link #traverse}
+ * @param state2 same as the state2 supplied to {@link #traverse}
+ */
+ default void onFinish(InsetsState state1, InsetsState state2) { }
+ }
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 1eb87c9..0bce710 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -20,6 +20,7 @@
import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_SUBLAYER;
import static android.view.WindowManagerPolicyConstants.APPLICATION_PANEL_SUBLAYER;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
@@ -51,6 +52,8 @@
import com.android.internal.view.SurfaceCallbackHelper;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;
@@ -117,6 +120,36 @@
* may not blend properly as a consequence of not applying alpha to the surface content directly.
*/
public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCallback {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"SURFACE_LIFECYCLE_"},
+ value = {SURFACE_LIFECYCLE_DEFAULT,
+ SURFACE_LIFECYCLE_FOLLOWS_VISIBILITY,
+ SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT})
+ public @interface SurfaceLifecycleStrategy {}
+
+ /**
+ * Default lifecycle of the Surface owned by this SurfaceView.
+ *
+ * The default lifecycle matches {@link #SURFACE_LIFECYCLE_FOLLOWS_VISIBILITY}.
+ */
+ public static final int SURFACE_LIFECYCLE_DEFAULT = 0;
+
+ /**
+ * The Surface lifecycle is tied to SurfaceView visibility.
+ *
+ * The Surface is created when the SurfaceView becomes visible, and is destroyed when the
+ * SurfaceView is no longer visible.
+ */
+ public static final int SURFACE_LIFECYCLE_FOLLOWS_VISIBILITY = 1;
+
+ /**
+ * The Surface lifecycle is tied to SurfaceView attachment.
+ * The Surface is created when the SurfaceView first becomes attached, but is not destroyed
+ * until this SurfaceView has been detached from the current window.
+ */
+ public static final int SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT = 2;
+
private static final String TAG = "SurfaceView";
private static final boolean DEBUG = false;
private static final boolean DEBUG_POSITION = false;
@@ -151,6 +184,11 @@
SurfaceControl mBackgroundControl;
private boolean mDisableBackgroundLayer = false;
+ @SurfaceLifecycleStrategy
+ private int mRequestedSurfaceLifecycleStrategy = SURFACE_LIFECYCLE_DEFAULT;
+ @SurfaceLifecycleStrategy
+ private int mSurfaceLifecycleStrategy = SURFACE_LIFECYCLE_DEFAULT;
+
/**
* We use this lock to protect access to mSurfaceControl. Both are accessed on the UI
* thread and the render thread via RenderNode.PositionUpdateListener#positionLost.
@@ -413,6 +451,7 @@
observer.removeOnPreDrawListener(mDrawListener);
mGlobalListenersAdded = false;
}
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " Detaching SV");
mRequestedVisible = false;
@@ -699,6 +738,20 @@
}
}
+ /**
+ * Controls the lifecycle of the Surface owned by this SurfaceView.
+ *
+ * <p>By default, the lifecycycle strategy employed by the SurfaceView is
+ * {@link #SURFACE_LIFECYCLE_DEFAULT}.
+ *
+ * @param lifecycleStrategy The strategy for the lifecycle of the Surface owned by this
+ * SurfaceView.
+ */
+ public void setSurfaceLifecycle(@SurfaceLifecycleStrategy int lifecycleStrategy) {
+ mRequestedSurfaceLifecycleStrategy = lifecycleStrategy;
+ updateSurface();
+ }
+
private void updateOpaqueFlag() {
if (!PixelFormat.formatHasAlpha(mRequestedFormat)) {
mSurfaceFlags |= SurfaceControl.OPAQUE;
@@ -780,7 +833,7 @@
mSurfaceLock.lock();
try {
- mDrawingStopped = !mVisible;
+ mDrawingStopped = !surfaceShouldExist();
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ "Cur surface: " + mSurface);
@@ -887,6 +940,20 @@
return realSizeChanged;
}
+ private boolean requiresSurfaceControlCreation(boolean formatChanged, boolean visibleChanged) {
+ if (mSurfaceLifecycleStrategy == SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT) {
+ return (mSurfaceControl == null || formatChanged) && mAttachedToWindow;
+ }
+
+ return (mSurfaceControl == null || formatChanged || visibleChanged) && mRequestedVisible;
+ }
+
+ private boolean surfaceShouldExist() {
+ final boolean respectVisibility =
+ mSurfaceLifecycleStrategy != SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT;
+ return mVisible || (!respectVisibility && mAttachedToWindow);
+ }
+
/** @hide */
protected void updateSurface() {
if (!mHaveFrame) {
@@ -921,8 +988,7 @@
final boolean formatChanged = mFormat != mRequestedFormat;
final boolean visibleChanged = mVisible != mRequestedVisible;
final boolean alphaChanged = mAlpha != alpha;
- final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged)
- && mRequestedVisible;
+ final boolean creating = requiresSurfaceControlCreation(formatChanged, visibleChanged);
final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility;
getLocationInWindow(mLocation);
@@ -933,10 +999,13 @@
final boolean hintChanged = (viewRoot.getBufferTransformHint() != mTransformHint)
&& mRequestedVisible;
final boolean relativeZChanged = mSubLayer != mRequestedSubLayer;
+ final boolean surfaceLifecycleStrategyChanged =
+ mSurfaceLifecycleStrategy != mRequestedSurfaceLifecycleStrategy;
if (creating || formatChanged || sizeChanged || visibleChanged
|| alphaChanged || windowVisibleChanged || positionChanged
- || layoutSizeChanged || hintChanged || relativeZChanged) {
+ || layoutSizeChanged || hintChanged || relativeZChanged || !mAttachedToWindow
+ || surfaceLifecycleStrategyChanged) {
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ "Changes: creating=" + creating
@@ -945,7 +1014,10 @@
+ " hint=" + hintChanged
+ " visible=" + visibleChanged
+ " left=" + (mWindowSpaceLeft != mLocation[0])
- + " top=" + (mWindowSpaceTop != mLocation[1]));
+ + " top=" + (mWindowSpaceTop != mLocation[1])
+ + " z=" + relativeZChanged
+ + " attached=" + mAttachedToWindow
+ + " lifecycleStrategy=" + surfaceLifecycleStrategyChanged);
try {
mVisible = mRequestedVisible;
@@ -959,6 +1031,9 @@
mTransformHint = viewRoot.getBufferTransformHint();
mSubLayer = mRequestedSubLayer;
+ final int previousSurfaceLifecycleStrategy = mSurfaceLifecycleStrategy;
+ mSurfaceLifecycleStrategy = mRequestedSurfaceLifecycleStrategy;
+
mScreenRect.left = mWindowSpaceLeft;
mScreenRect.top = mWindowSpaceTop;
mScreenRect.right = mWindowSpaceLeft + getWidth();
@@ -1000,15 +1075,27 @@
SurfaceHolder.Callback[] callbacks = null;
final boolean surfaceChanged = creating;
- if (mSurfaceCreated && (surfaceChanged || (!mVisible && visibleChanged))) {
- mSurfaceCreated = false;
- notifySurfaceDestroyed();
+ final boolean respectVisibility =
+ mSurfaceLifecycleStrategy != SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT;
+ final boolean previouslyDidNotRespectVisibility =
+ previousSurfaceLifecycleStrategy
+ == SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT;
+ final boolean lifecycleNewlyRespectsVisibility = respectVisibility
+ && previouslyDidNotRespectVisibility;
+ if (mSurfaceCreated) {
+ if (surfaceChanged || (!respectVisibility && !mAttachedToWindow)
+ || (respectVisibility && !mVisible
+ && (visibleChanged || lifecycleNewlyRespectsVisibility))) {
+ mSurfaceCreated = false;
+ notifySurfaceDestroyed();
+ }
}
copySurface(creating /* surfaceControlCreated */, sizeChanged);
- if (mVisible && mSurface.isValid()) {
- if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
+ if (surfaceShouldExist() && mSurface.isValid()) {
+ if (!mSurfaceCreated
+ && (surfaceChanged || (respectVisibility && visibleChanged))) {
mSurfaceCreated = true;
mIsCreating = true;
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
@@ -1019,7 +1106,7 @@
}
}
if (creating || formatChanged || sizeChanged || hintChanged
- || visibleChanged || realSizeChanged) {
+ || (respectVisibility && visibleChanged) || realSizeChanged) {
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ "surfaceChanged -- format=" + mFormat
+ " w=" + myWidth + " h=" + myHeight);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9fa6c05..06dab15 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17,6 +17,7 @@
package android.view;
import static android.content.res.Resources.ID_NULL;
+import static android.os.Trace.TRACE_TAG_APP;
import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
@@ -998,6 +999,22 @@
private static boolean sAcceptZeroSizeDragShadow;
/**
+ * When true, measure and layout passes of all the newly attached views will be logged with
+ * {@link Trace}, so we can better debug jank due to complex view hierarchies.
+ */
+ private static boolean sTraceLayoutSteps;
+
+ /**
+ * When not null, emits a {@link Trace} instant event and the stacktrace every time a relayout
+ * of a class having this name happens.
+ */
+ private static String sTraceRequestLayoutClass;
+
+ /** Used to avoid computing the full strings each time when layout tracing is enabled. */
+ @Nullable
+ private ViewTraversalTracingStrings mTracingStrings;
+
+ /**
* Prior to R, {@link #dispatchApplyWindowInsets} had an issue:
* <p>The modified insets changed by {@link #onApplyWindowInsets} were passed to the
* entire view hierarchy in prefix order, including siblings as well as siblings of parents
@@ -3583,6 +3600,9 @@
* 1 PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE
* 1 PFLAG4_DRAG_A11Y_STARTED
* 1 PFLAG4_AUTO_HANDWRITING_INITIATION_ENABLED
+ * 1 PFLAG4_IMPORTANT_FOR_CREDENTIAL_MANAGER
+ * 1 PFLAG4_TRAVERSAL_TRACING_ENABLED
+ * 1 PFLAG4_RELAYOUT_TRACING_ENABLED
* |-------|-------|-------|-------|
*/
@@ -3669,6 +3689,18 @@
*/
private static final int PFLAG4_IMPORTANT_FOR_CREDENTIAL_MANAGER = 0x000020000;
+ /**
+ * When set, measure and layout passes of this view will be logged with {@link Trace}, so we
+ * can better debug jank due to complex view hierarchies.
+ */
+ private static final int PFLAG4_TRAVERSAL_TRACING_ENABLED = 0x000040000;
+
+ /**
+ * When set, emits a {@link Trace} instant event and stacktrace every time a requestLayout of
+ * this class happens.
+ */
+ private static final int PFLAG4_RELAYOUT_TRACING_ENABLED = 0x000080000;
+
/* End of masks for mPrivateFlags4 */
/** @hide */
@@ -6653,6 +6685,15 @@
out.append(mRight);
out.append(',');
out.append(mBottom);
+ appendId(out);
+ if (mAutofillId != null) {
+ out.append(" aid="); out.append(mAutofillId);
+ }
+ out.append("}");
+ return out.toString();
+ }
+
+ void appendId(StringBuilder out) {
final int id = getId();
if (id != NO_ID) {
out.append(" #");
@@ -6684,11 +6725,6 @@
}
}
}
- if (mAutofillId != null) {
- out.append(" aid="); out.append(mAutofillId);
- }
- out.append("}");
- return out.toString();
}
/**
@@ -8883,7 +8919,8 @@
* @hide
*/
@UnsupportedAppUsage
- public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
+ @TestApi
+ public void getBoundsOnScreen(@NonNull Rect outRect, boolean clipToParent) {
if (mAttachInfo == null) {
return;
}
@@ -8891,6 +8928,7 @@
getBoundsToScreenInternal(position, clipToParent);
outRect.set(Math.round(position.left), Math.round(position.top),
Math.round(position.right), Math.round(position.bottom));
+ mAttachInfo.mViewRootImpl.applyViewBoundsSandboxingIfNeeded(outRect);
}
/**
@@ -15928,7 +15966,8 @@
* @hide
*/
@UnsupportedAppUsage
- public void getWindowDisplayFrame(Rect outRect) {
+ @TestApi
+ public void getWindowDisplayFrame(@NonNull Rect outRect) {
if (mAttachInfo != null) {
mAttachInfo.mViewRootImpl.getDisplayFrame(outRect);
return;
@@ -21154,6 +21193,14 @@
if (isFocused()) {
notifyFocusChangeToImeFocusController(true /* hasFocus */);
}
+
+ if (sTraceLayoutSteps) {
+ setTraversalTracingEnabled(true);
+ }
+ if (sTraceRequestLayoutClass != null
+ && sTraceRequestLayoutClass.equals(getClass().getSimpleName())) {
+ setRelayoutTracingEnabled(true);
+ }
}
/**
@@ -24053,6 +24100,30 @@
return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
}
+ /**
+ * Enable measure/layout debugging on traces.
+ *
+ * @see Trace
+ * @hide
+ */
+ public static void setTraceLayoutSteps(boolean traceLayoutSteps) {
+ sTraceLayoutSteps = traceLayoutSteps;
+ }
+
+ /**
+ * Enable request layout tracing classes with {@code s} simple name.
+ * <p>
+ * When set, a {@link Trace} instant event and a log with the stacktrace is emitted every
+ * time a requestLayout of a class matching {@code s} name happens.
+ * This applies only to views attached from this point onwards.
+ *
+ * @see Trace#instant(long, String)
+ * @hide
+ */
+ public static void setTracedRequestLayoutClassClass(String s) {
+ sTraceRequestLayoutClass = s;
+ }
+
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
Insets parentInsets = mParent instanceof View ?
((View) mParent).getOpticalInsets() : Insets.NONE;
@@ -24087,7 +24158,13 @@
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
+ if (isTraversalTracingEnabled()) {
+ Trace.beginSection(mTracingStrings.onMeasureBeforeLayout);
+ }
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
+ if (isTraversalTracingEnabled()) {
+ Trace.endSection();
+ }
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
@@ -24100,7 +24177,13 @@
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
+ if (isTraversalTracingEnabled()) {
+ Trace.beginSection(mTracingStrings.onLayout);
+ }
onLayout(changed, l, t, r, b);
+ if (isTraversalTracingEnabled()) {
+ Trace.endSection();
+ }
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
@@ -26093,7 +26176,11 @@
getLocationInWindow(outLocation);
final AttachInfo info = mAttachInfo;
- if (info != null) {
+
+ // Need to offset the outLocation with the window bounds, but only if "Sandboxing View
+ // Bounds APIs" is disabled. If this override is enabled, it sandboxes {@link outLocation}
+ // within activity bounds.
+ if (info != null && !info.mViewRootImpl.isViewBoundsSandboxingEnabled()) {
outLocation[0] += info.mWindowLeft;
outLocation[1] += info.mWindowTop;
}
@@ -26660,6 +26747,25 @@
return (viewRoot != null && viewRoot.isInLayout());
}
+ /** To be used only for debugging purposes. */
+ private void printStackStrace(String name) {
+ Log.d(VIEW_LOG_TAG, "---- ST:" + name);
+
+ StringBuilder sb = new StringBuilder();
+ StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
+ int startIndex = 1;
+ int endIndex = Math.min(stackTraceElements.length, startIndex + 20); // max 20 entries.
+ for (int i = startIndex; i < endIndex; i++) {
+ StackTraceElement s = stackTraceElements[i];
+ sb.append(s.getMethodName())
+ .append("(")
+ .append(s.getFileName())
+ .append(":")
+ .append(s.getLineNumber())
+ .append(") <- ");
+ }
+ Log.d(VIEW_LOG_TAG, name + ": " + sb);
+ }
/**
* Call this when something has changed which has invalidated the
* layout of this view. This will schedule a layout pass of the view
@@ -26673,6 +26779,12 @@
*/
@CallSuper
public void requestLayout() {
+ if (isRelayoutTracingEnabled()) {
+ Trace.instantForTrack(TRACE_TAG_APP, "requestLayoutTracing",
+ mTracingStrings.classSimpleName);
+ printStackStrace(mTracingStrings.requestLayoutStacktracePrefix);
+ }
+
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
@@ -26766,8 +26878,14 @@
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
+ if (isTraversalTracingEnabled()) {
+ Trace.beginSection(mTracingStrings.onMeasure);
+ }
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
+ if (isTraversalTracingEnabled()) {
+ Trace.endSection();
+ }
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
@@ -32005,6 +32123,38 @@
.isStylusHandwritingAvailable();
}
+ private void setTraversalTracingEnabled(boolean enabled) {
+ if (enabled) {
+ if (mTracingStrings == null) {
+ mTracingStrings = new ViewTraversalTracingStrings(this);
+ }
+ mPrivateFlags4 |= PFLAG4_TRAVERSAL_TRACING_ENABLED;
+ } else {
+ mPrivateFlags4 &= ~PFLAG4_TRAVERSAL_TRACING_ENABLED;
+ }
+ }
+
+ private boolean isTraversalTracingEnabled() {
+ return (mPrivateFlags4 & PFLAG4_TRAVERSAL_TRACING_ENABLED)
+ == PFLAG4_TRAVERSAL_TRACING_ENABLED;
+ }
+
+ private void setRelayoutTracingEnabled(boolean enabled) {
+ if (enabled) {
+ if (mTracingStrings == null) {
+ mTracingStrings = new ViewTraversalTracingStrings(this);
+ }
+ mPrivateFlags4 |= PFLAG4_RELAYOUT_TRACING_ENABLED;
+ } else {
+ mPrivateFlags4 &= ~PFLAG4_RELAYOUT_TRACING_ENABLED;
+ }
+ }
+
+ private boolean isRelayoutTracingEnabled() {
+ return (mPrivateFlags4 & PFLAG4_RELAYOUT_TRACING_ENABLED)
+ == PFLAG4_RELAYOUT_TRACING_ENABLED;
+ }
+
/**
* Collects a {@link ViewTranslationRequest} which represents the content to be translated in
* the view.
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index f51d9ba..c96d298 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -27,6 +27,7 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.hardware.input.InputManager;
import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
@@ -232,6 +233,12 @@
*/
private static final int MAXIMUM_FLING_VELOCITY = 8000;
+ /** Value used as a minimum fling velocity, when fling is not supported. */
+ private static final int NO_FLING_MIN_VELOCITY = Integer.MAX_VALUE;
+
+ /** Value used as a maximum fling velocity, when fling is not supported. */
+ private static final int NO_FLING_MAX_VELOCITY = Integer.MIN_VALUE;
+
/**
* Delay before dispatching a recurring accessibility event in milliseconds.
* This delay guarantees that a recurring event will be send at most once
@@ -333,6 +340,8 @@
private final int mFadingEdgeLength;
private final int mMinimumFlingVelocity;
private final int mMaximumFlingVelocity;
+ private final int mMinimumRotaryEncoderFlingVelocity;
+ private final int mMaximumRotaryEncoderFlingVelocity;
private final int mScrollbarSize;
private final int mTouchSlop;
private final int mHandwritingSlop;
@@ -378,6 +387,8 @@
mFadingEdgeLength = FADING_EDGE_LENGTH;
mMinimumFlingVelocity = MINIMUM_FLING_VELOCITY;
mMaximumFlingVelocity = MAXIMUM_FLING_VELOCITY;
+ mMinimumRotaryEncoderFlingVelocity = MINIMUM_FLING_VELOCITY;
+ mMaximumRotaryEncoderFlingVelocity = MAXIMUM_FLING_VELOCITY;
mScrollbarSize = SCROLL_BAR_SIZE;
mTouchSlop = TOUCH_SLOP;
mHandwritingSlop = HANDWRITING_SLOP;
@@ -504,6 +515,19 @@
com.android.internal.R.dimen.config_viewMinFlingVelocity);
mMaximumFlingVelocity = res.getDimensionPixelSize(
com.android.internal.R.dimen.config_viewMaxFlingVelocity);
+
+ int configMinRotaryEncoderFlingVelocity = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_viewMinRotaryEncoderFlingVelocity);
+ int configMaxRotaryEncoderFlingVelocity = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_viewMaxRotaryEncoderFlingVelocity);
+ if (configMinRotaryEncoderFlingVelocity < 0 || configMaxRotaryEncoderFlingVelocity < 0) {
+ mMinimumRotaryEncoderFlingVelocity = NO_FLING_MIN_VELOCITY;
+ mMaximumRotaryEncoderFlingVelocity = NO_FLING_MAX_VELOCITY;
+ } else {
+ mMinimumRotaryEncoderFlingVelocity = configMinRotaryEncoderFlingVelocity;
+ mMaximumRotaryEncoderFlingVelocity = configMaxRotaryEncoderFlingVelocity;
+ }
+
mGlobalActionsKeyTimeout = res.getInteger(
com.android.internal.R.integer.config_globalActionsKeyTimeout);
@@ -1077,6 +1101,98 @@
}
/**
+ * Minimum absolute value of velocity to initiate a fling for a motion generated by an
+ * {@link InputDevice} with an id of {@code inputDeviceId}, from an input {@code source} and on
+ * a given motion event {@code axis}.
+ *
+ * <p>Before utilizing this method to get a minimum fling velocity for a motion generated by the
+ * input device, scale the velocity of the motion events generated by the input device to pixels
+ * per second.
+ *
+ * <p>For instance, if you tracked {@link MotionEvent#AXIS_SCROLL} vertical velocities generated
+ * from a {@link InputDevice#SOURCE_ROTARY_ENCODER}, the velocity returned from
+ * {@link VelocityTracker} will be in the units with which the axis values were reported in the
+ * motion event. Before comparing that velocity against the minimum fling velocity specified
+ * here, make sure that the {@link MotionEvent#AXIS_SCROLL} velocity from the tracker is
+ * calculated in "units per second" (see {@link VelocityTracker#computeCurrentVelocity(int)},
+ * {@link VelocityTracker#computeCurrentVelocity(int, float)} to adjust your velocity
+ * computations to "per second"), and use {@link #getScaledVerticalScrollFactor} to change this
+ * velocity value to "pixels/second".
+ *
+ * <p>If the provided {@code inputDeviceId} is not valid, or if the input device whose ID is
+ * provided does not support the given motion event source and/or axis, this method will return
+ * {@code Integer.MAX_VALUE}.
+ *
+ * <h3>Obtaining the correct arguments for this method call</h3>
+ * <p><b>inputDeviceId</b>: if calling this method in response to a {@link MotionEvent}, use
+ * the device ID that is reported by the event, which can be obtained using
+ * {@link MotionEvent#getDeviceId()}. Otherwise, use a valid ID that is obtained from
+ * {@link InputDevice#getId()}, or from an {@link InputManager} instance
+ * ({@link InputManager#getInputDeviceIds()} gives all the valid input device IDs).
+ *
+ * <p><b>axis</b>: a {@link MotionEvent} may report data for multiple axes, and each axis may
+ * have multiple data points for different pointers. Use the axis for which you obtained the
+ * velocity for ({@link VelocityTracker} lets you calculate velocities for a specific axis. Use
+ * the axis for which you calculated velocity). You can use
+ * {@link InputDevice#getMotionRanges()} to get all the {@link InputDevice.MotionRange}s for the
+ * {@link InputDevice}, from which you can derive all the valid axes for the device.
+ *
+ * <p><b>source</b>: use {@link MotionEvent#getSource()} if calling this method in response to a
+ * {@link MotionEvent}. Otherwise, use a valid source for the {@link InputDevice}. You can use
+ * {@link InputDevice#getMotionRanges()} to get all the {@link InputDevice.MotionRange}s for the
+ * {@link InputDevice}, from which you can derive all the valid sources for the device.
+ *
+ *
+ * <p>This method optimizes calls over multiple input device IDs, so caching the return value of
+ * the method is not necessary if you are handling multiple input devices.
+ *
+ * @param inputDeviceId the ID of the {@link InputDevice} that generated the motion triggering
+ * fling.
+ * @param axis the axis on which the motion triggering the fling happened. This axis should be
+ * a valid axis that can be reported by the provided input device from the provided
+ * input device source.
+ * @param source the input source of the motion causing fling. This source should be a valid
+ * source for the {@link InputDevice} whose ID is {@code inputDeviceId}.
+ *
+ * @return the minimum velocity, in pixels/second, to trigger fling.
+ *
+ * @see InputDevice#getMotionRange(int, int)
+ * @see InputDevice#getMotionRanges()
+ * @see VelocityTracker#getAxisVelocity(int, int)
+ * @see VelocityTracker#getAxisVelocity(int)
+ */
+ public int getScaledMinimumFlingVelocity(int inputDeviceId, int axis, int source) {
+ if (!isInputDeviceInfoValid(inputDeviceId, axis, source)) return NO_FLING_MIN_VELOCITY;
+
+ if (source == InputDevice.SOURCE_ROTARY_ENCODER) return mMinimumRotaryEncoderFlingVelocity;
+
+ return mMinimumFlingVelocity;
+ }
+
+ /**
+ * Maximum absolute value of velocity to initiate a fling for a motion generated by an
+ * {@link InputDevice} with an id of {@code inputDeviceId}, from an input {@code source} and on
+ * a given motion event {@code axis}.
+ *
+ * <p>Similar to {@link #getScaledMinimumFlingVelocity(int, int, int)}, but for maximum fling
+ * velocity, instead of minimum. Also, unlike that method which returns
+ * {@code Integer.MAX_VALUE} for bad input device ID, source and/or motion event axis inputs,
+ * this method returns {@code Integer.MIN_VALUE} for such bad inputs.
+ */
+ public int getScaledMaximumFlingVelocity(int inputDeviceId, int axis, int source) {
+ if (!isInputDeviceInfoValid(inputDeviceId, axis, source)) return NO_FLING_MAX_VELOCITY;
+
+ if (source == InputDevice.SOURCE_ROTARY_ENCODER) return mMaximumRotaryEncoderFlingVelocity;
+
+ return mMaximumFlingVelocity;
+ }
+
+ private static boolean isInputDeviceInfoValid(int id, int axis, int source) {
+ InputDevice device = InputManager.getInstance().getInputDevice(id);
+ return device != null && device.getMotionRange(axis, source) != null;
+ }
+
+ /**
* Check if shortcuts should be displayed in menus.
*
* @return {@code True} if shortcuts should be displayed in menus.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index de946a4..872b4f9 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,13 +16,14 @@
package android.view;
+import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS;
import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED;
import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND;
import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.InputDevice.SOURCE_CLASS_NONE;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -79,6 +80,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
@@ -98,6 +100,7 @@
import android.app.ICompatCameraControlCallback;
import android.app.ResourcesManager;
import android.app.WindowConfiguration;
+import android.app.compat.CompatChanges;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -709,6 +712,7 @@
// These are accessed by multiple threads.
final Rect mWinFrame; // frame given by window manager.
+ private final Rect mLastLayoutFrame;
Rect mOverrideInsetsFrame;
final Rect mPendingBackDropFrame = new Rect();
@@ -888,6 +892,15 @@
private boolean mRelayoutRequested;
+ /**
+ * Whether sandboxing of {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.View#getLocationOnScreen},
+ * {@link android.view.View#getWindowDisplayFrame} and
+ * {@link android.view.View#getWindowVisibleDisplayFrame}
+ * within Activity bounds is enabled for the current application.
+ */
+ private final boolean mViewBoundsSandboxingEnabled;
+
private int mLastTransformHint = Integer.MIN_VALUE;
private AccessibilityWindowAttributes mAccessibilityWindowAttributes;
@@ -951,6 +964,7 @@
mHeight = -1;
mDirty = new Rect();
mWinFrame = new Rect();
+ mLastLayoutFrame = new Rect();
mWindow = new W(this);
mLeashToken = new Binder();
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
@@ -978,6 +992,8 @@
mViewConfiguration,
mContext.getSystemService(InputMethodManager.class));
+ mViewBoundsSandboxingEnabled = getViewBoundsSandboxingEnabled();
+
String processorOverrideName = context.getResources().getString(
R.string.config_inputEventCompatProcessorOverrideClassName);
if (processorOverrideName.isEmpty()) {
@@ -1315,7 +1331,7 @@
UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH,
mInsetsController.getRequestedVisibleTypes(), 1f /* compactScale */,
mTmpFrames);
- setFrame(mTmpFrames.frame);
+ setFrame(mTmpFrames.frame, true /* withinRelayout */);
registerBackCallbackOnWindow();
if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow);
if (res < WindowManagerGlobal.ADD_OKAY) {
@@ -1842,7 +1858,7 @@
onMovedToDisplay(displayId, mLastConfigurationFromResources);
}
- setFrame(frame);
+ setFrame(frame, false /* withinRelayout */);
mTmpFrames.displayFrame.set(displayFrame);
if (mTmpFrames.attachedFrame != null && attachedFrame != null) {
mTmpFrames.attachedFrame.set(attachedFrame);
@@ -5836,7 +5852,7 @@
mTmpFrames.frame.right = l + w;
mTmpFrames.frame.top = t;
mTmpFrames.frame.bottom = t + h;
- setFrame(mTmpFrames.frame);
+ setFrame(mTmpFrames.frame, false /* withinRelayout */);
maybeHandleWindowMove(mWinFrame);
}
break;
@@ -8343,7 +8359,7 @@
// If the position and the size of the frame are both changed, it will trigger a BLAST
// sync, and we still need to call relayout to obtain the syncSeqId. Otherwise, we just
// need to send attributes via relayoutAsync.
- final Rect oldFrame = mWinFrame;
+ final Rect oldFrame = mLastLayoutFrame;
final Rect newFrame = mTmpFrames.frame;
final boolean positionChanged =
newFrame.top != oldFrame.top || newFrame.left != oldFrame.left;
@@ -8473,7 +8489,7 @@
params.restore();
}
- setFrame(mTmpFrames.frame);
+ setFrame(mTmpFrames.frame, true /* withinRelayout */);
return relayoutResult;
}
@@ -8508,8 +8524,18 @@
mIsSurfaceOpaque = opaque;
}
- private void setFrame(Rect frame) {
+ /**
+ * Set the mWinFrame of this window.
+ * @param frame the new frame of this window.
+ * @param withinRelayout {@code true} if this setting is within the relayout, or is the initial
+ * setting. That will make sure in the relayout process, we always compare
+ * the window frame with the last processed window frame.
+ */
+ private void setFrame(Rect frame, boolean withinRelayout) {
mWinFrame.set(frame);
+ if (withinRelayout) {
+ mLastLayoutFrame.set(frame);
+ }
final WindowConfiguration winConfig = getCompatWindowConfiguration();
mPendingBackDropFrame.set(mPendingDragResizing && !winConfig.useWindowFrameForBackdrop()
@@ -8544,6 +8570,9 @@
*/
void getDisplayFrame(Rect outFrame) {
outFrame.set(mTmpFrames.displayFrame);
+ // Apply sandboxing here (in getter) due to possible layout updates on the client after
+ // {@link #mTmpFrames.displayFrame} is received from the server.
+ applyViewBoundsSandboxingIfNeeded(outFrame);
}
/**
@@ -8560,6 +8589,60 @@
outFrame.top += insets.top;
outFrame.right -= insets.right;
outFrame.bottom -= insets.bottom;
+ // Apply sandboxing here (in getter) due to possible layout updates on the client after
+ // {@link #mTmpFrames.displayFrame} is received from the server.
+ applyViewBoundsSandboxingIfNeeded(outFrame);
+ }
+
+ /**
+ * Offset outRect to make it sandboxed within Window's bounds.
+ *
+ * <p>This is used by {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.ViewRootImpl#getDisplayFrame} and
+ * {@link android.view.ViewRootImpl#getWindowVisibleDisplayFrame}, which are invoked by
+ * {@link android.view.View#getWindowDisplayFrame} and
+ * {@link android.view.View#getWindowVisibleDisplayFrame}, as well as
+ * {@link android.view.ViewDebug#captureLayers} for debugging.
+ */
+ void applyViewBoundsSandboxingIfNeeded(final Rect inOutRect) {
+ if (isViewBoundsSandboxingEnabled()) {
+ inOutRect.offset(-mAttachInfo.mWindowLeft, -mAttachInfo.mWindowTop);
+ }
+ }
+
+ /**
+ * Whether the sanboxing of the {@link android.view.View} APIs is enabled.
+ *
+ * <p>This is called by {@link #applyViewBoundsSandboxingIfNeeded} and
+ * {@link android.view.View#getLocationOnScreen} to check if there is a need to add
+ * {@link android.view.View.AttachInfo.mWindowLeft} and
+ * {@link android.view.View.AttachInfo.mWindowTop} offsets.
+ */
+ boolean isViewBoundsSandboxingEnabled() {
+ return mViewBoundsSandboxingEnabled;
+ }
+
+ private boolean getViewBoundsSandboxingEnabled() {
+ if (!CompatChanges.isChangeEnabled(OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS)) {
+ // OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS change-id is disabled.
+ return false;
+ }
+
+ // OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS is enabled by the device manufacturer.
+ try {
+ final List<PackageManager.Property> properties = mContext.getPackageManager()
+ .queryApplicationProperty(PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS);
+
+ final boolean isOptedOut = !properties.isEmpty() && !properties.get(0).getBoolean();
+ if (isOptedOut) {
+ // PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS is disabled by the app devs.
+ return false;
+ }
+ } catch (RuntimeException e) {
+ // remote exception.
+ }
+
+ return true;
}
/**
@@ -8957,7 +9040,7 @@
if (mTranslator != null) {
mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
}
- if (insetsState.getSourceOrDefaultVisibility(ITYPE_IME)) {
+ if (insetsState.isSourceOrDefaultVisible(ID_IME, Type.ime())) {
ImeTracing.getInstance().triggerClientDump("ViewRootImpl#dispatchResized",
getInsetsController().getHost().getInputMethodManager(), null /* icProto */);
}
@@ -8989,7 +9072,7 @@
mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
mTranslator.translateSourceControlsInScreenToAppWindow(activeControls);
}
- if (insetsState != null && insetsState.getSourceOrDefaultVisibility(ITYPE_IME)) {
+ if (insetsState != null && insetsState.isSourceOrDefaultVisible(ID_IME, Type.ime())) {
ImeTracing.getInstance().triggerClientDump("ViewRootImpl#dispatchInsetsControlChanged",
getInsetsController().getHost().getInputMethodManager(), null /* icProto */);
}
@@ -11428,33 +11511,25 @@
});
}
- private class VRISurfaceSyncGroup extends SurfaceSyncGroup {
- VRISurfaceSyncGroup(String name) {
- super(name);
- }
-
- @Override
- public void onSyncReady() {
- Runnable runnable = () -> {
- mNumPausedForSync--;
- if (!mIsInTraversal && mNumPausedForSync == 0) {
- scheduleTraversals();
- }
- };
-
- if (Thread.currentThread() == mThread) {
- runnable.run();
- } else {
- mHandler.post(runnable);
- }
- }
- }
-
@Override
public SurfaceSyncGroup getOrCreateSurfaceSyncGroup() {
boolean newSyncGroup = false;
if (mActiveSurfaceSyncGroup == null) {
- mActiveSurfaceSyncGroup = new VRISurfaceSyncGroup(mTag);
+ mActiveSurfaceSyncGroup = new SurfaceSyncGroup(mTag);
+ mActiveSurfaceSyncGroup.setAddedToSyncListener(() -> {
+ Runnable runnable = () -> {
+ mNumPausedForSync--;
+ if (!mIsInTraversal && mNumPausedForSync == 0) {
+ scheduleTraversals();
+ }
+ };
+
+ if (Thread.currentThread() == mThread) {
+ runnable.run();
+ } else {
+ mHandler.post(runnable);
+ }
+ });
updateSyncInProgressCount(mActiveSurfaceSyncGroup);
newSyncGroup = true;
}
diff --git a/core/java/android/view/ViewTraversalTracingStrings.java b/core/java/android/view/ViewTraversalTracingStrings.java
new file mode 100644
index 0000000..7dde87b
--- /dev/null
+++ b/core/java/android/view/ViewTraversalTracingStrings.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.os.Trace;
+
+/**
+ * Keeps and caches strings used to trace {@link View} traversals.
+ * <p>
+ * This is done to avoid expensive computations of them every time, which can improve performance.
+ */
+class ViewTraversalTracingStrings {
+
+ /** {@link Trace} tag used to mark {@link View#onMeasure(int, int)}. */
+ public final String onMeasure;
+
+ /** {@link Trace} tag used to mark {@link View#onLayout(boolean, int, int, int, int)}. */
+ public final String onLayout;
+
+ /** Caches the view simple name to avoid re-computations. */
+ public final String classSimpleName;
+
+ /** Prefix for request layout stacktraces output in logs. */
+ public final String requestLayoutStacktracePrefix;
+
+ /** {@link Trace} tag used to mark {@link View#onMeasure(int, int)} happening before layout. */
+ public final String onMeasureBeforeLayout;
+
+ /**
+ * @param v {@link View} from where to get the class name.
+ */
+ ViewTraversalTracingStrings(View v) {
+ String className = v.getClass().getSimpleName();
+ classSimpleName = className;
+ onMeasureBeforeLayout = getTraceName("onMeasureBeforeLayout", className, v);
+ onMeasure = getTraceName("onMeasure", className, v);
+ onLayout = getTraceName("onLayout", className, v);
+ requestLayoutStacktracePrefix = "requestLayout " + className;
+ }
+
+ private String getTraceName(String sectionName, String className, View v) {
+ StringBuilder out = new StringBuilder();
+ out.append(sectionName);
+ out.append(" ");
+ out.append(className);
+ v.appendId(out);
+ return out.substring(0, Math.min(out.length() - 1, Trace.MAX_SECTION_NAME_LEN - 1));
+ }
+}
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index 7077804..5ec5219 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -16,7 +16,7 @@
package android.view;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
@@ -89,7 +89,7 @@
if (attachedWindowFrame == null) {
outParentFrame.set(outDisplayFrame);
if ((pfl & PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME) != 0) {
- final InsetsSource source = state.peekSource(ITYPE_IME);
+ final InsetsSource source = state.peekSource(ID_IME);
if (source != null) {
outParentFrame.inset(source.calculateInsets(
outParentFrame, false /* ignoreVisibility */));
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 9bec409..3d3f4f6 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -815,8 +815,8 @@
}
/**
- * Activity level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the activity can be opted-in or opted-out
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the app can be opted-in or opted-out
* from the compatibility treatment that avoids {@link
* android.app.Activity#setRequestedOrientation} loops. The loop can be trigerred by
* ignoreRequestedOrientation display setting enabled on the device or by the landscape natural
@@ -834,17 +834,17 @@
* <li>Camera compatibility force rotation treatment is active for the package.
* </ul>
*
- * <p>Setting this property to {@code false} informs the system that the activity must be
+ * <p>Setting this property to {@code false} informs the system that the app must be
* opted-out from the compatibility treatment even if the device manufacturer has opted the app
* into the treatment.
*
* <p><b>Syntax:</b>
* <pre>
- * <activity>
+ * <application>
* <property
* android:name="android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION"
* android:value="true|false"/>
- * </activity>
+ * </application>
* </pre>
*
* @hide
@@ -854,8 +854,81 @@
"android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
/**
- * Activity level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the activity should be excluded from the
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that it needs to be opted-out from the
+ * compatibility treatment that sandboxes {@link android.view.View} API.
+ *
+ * <p>The treatment can be enabled by device manufacturers for applications which misuse
+ * {@link android.view.View} APIs by expecting that
+ * {@link android.view.View#getLocationOnScreen},
+ * {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.View#getWindowVisibleDisplayFrame},
+ * {@link android.view.View#getWindowDisplayFrame}
+ * return coordinates as if an activity is positioned in the top-left corner of the screen, with
+ * left coordinate equal to 0. This may not be the case for applications in multi-window and in
+ * letterbox modes.
+ *
+ * <p>Setting this property to {@code false} informs the system that the application must be
+ * opted-out from the "Sandbox {@link android.view.View} API to Activity bounds" treatment even
+ * if the device manufacturer has opted the app into the treatment.
+ *
+ * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <application>
+ * <property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS"
+ * android:value="false"/>
+ * </application>
+ * </pre>
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS =
+ "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
+
+ /**
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the application can be opted-in or opted-out
+ * from the compatibility treatment that enables sending a fake focus event for unfocused
+ * resumed split screen activities. This is needed because some game engines wait to get
+ * focus before drawing the content of the app which isn't guaranteed by default in multi-window
+ * modes.
+ *
+ * <p>Device manufacturers can enable this treatment using their discretion on a per-device
+ * basis to improve display compatibility. The treatment also needs to be specifically enabled
+ * on a per-app basis afterwards. This can either be done by device manufacturers or developers.
+ *
+ * <p>With this property set to {@code true}, the system will apply the treatment only if the
+ * device manufacturer had previously enabled it on the device. A fake focus event will be sent
+ * to the app after it is resumed only if the app is in split-screen.
+ *
+ * <p>Setting this property to {@code false} informs the system that the activity must be
+ * opted-out from the compatibility treatment even if the device manufacturer has opted the app
+ * into the treatment.
+ *
+ * <p>If the property remains unset the system will apply the treatment only if it had
+ * previously been enabled both at the device and app level by the device manufacturer.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <application>
+ * <property
+ * android:name="android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS"
+ * android:value="true|false"/>
+ * </application>
+ * </pre>
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
+
+ /**
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the app should be excluded from the
* camera compatibility force rotation treatment.
*
* <p>The camera compatibility treatment aligns orientations of portrait app window and natural
@@ -880,11 +953,11 @@
*
* <p><b>Syntax:</b>
* <pre>
- * <activity>
+ * <application>
* <property
* android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION"
* android:value="true|false"/>
- * </activity>
+ * </application>
* </pre>
*
* @hide
@@ -894,8 +967,8 @@
"android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
/**
- * Activity level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the activity should be excluded
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the app should be excluded
* from the activity "refresh" after the camera compatibility force rotation treatment.
*
* <p>The camera compatibility treatment aligns orientations of portrait app window and natural
@@ -927,11 +1000,11 @@
*
* <p><b>Syntax:</b>
* <pre>
- * <activity>
+ * <application>
* <property
* android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH"
* android:value="true|false"/>
- * </activity>
+ * </application>
* </pre>
*
* @hide
@@ -941,7 +1014,7 @@
"android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
/**
- * Activity level {@link android.content.pm.PackageManager.Property PackageManager
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
* .Property} for an app to inform the system that the activity should be or shouldn't be
* "refreshed" after the camera compatibility force rotation treatment using "paused ->
* resumed" cycle rather than "stopped -> resumed".
@@ -977,11 +1050,11 @@
*
* <p><b>Syntax:</b>
* <pre>
- * <activity>
+ * <application>
* <property
* android:name="android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE"
* android:value="true|false"/>
- * </activity>
+ * </application>
* </pre>
*
* @hide
@@ -991,23 +1064,23 @@
"android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
/**
- * Activity level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the activity should be excluded from the
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the app should be excluded from the
* compatibility override for orientation set by the device manufacturer.
*
* <p>With this property set to {@code true} or unset, device manufacturers can override
- * orientation for the activity using their discretion to improve display compatibility.
+ * orientation for the app using their discretion to improve display compatibility.
*
* <p>With this property set to {@code false}, device manufactured per-app override for
* orientation won't be applied.
*
* <p><b>Syntax:</b>
* <pre>
- * <activity>
+ * <application>
* <property
* android:name="android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE"
* android:value="true|false"/>
- * </activity>
+ * </application>
* </pre>
*
* @hide
@@ -1017,8 +1090,8 @@
"android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
/**
- * Activity level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the activity should be opted-out from the
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the app should be opted-out from the
* compatibility override that fixes display orientation to landscape natural orientation when
* an activity is fullscreen.
*
@@ -1048,11 +1121,11 @@
*
* <p><b>Syntax:</b>
* <pre>
- * <activity>
+ * <application>
* <property
* android:name="android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE"
* android:value="true|false"/>
- * </activity>
+ * </application>
* </pre>
*
* @hide
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 5e323fa..8f270f5 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -361,6 +361,16 @@
* @throws NullPointerException if the target is {@code null}.
*/
@Override
+ public boolean requestCursorUpdates(@CursorUpdateMode int cursorUpdateMode,
+ @CursorUpdateFilter int cursorUpdateFilter) {
+ return mTarget.requestCursorUpdates(cursorUpdateMode, cursorUpdateFilter);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ @Override
public void requestTextBoundsInfo(
@NonNull RectF bounds, @NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<TextBoundsInfoResult> consumer) {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 1c18a73..136846a 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -37,6 +37,7 @@
import android.content.UndoOwner;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -101,6 +102,7 @@
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
+import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@@ -185,20 +187,34 @@
// Tag used when the Editor maintains its own separate UndoManager.
private static final String UNDO_OWNER_TAG = "Editor";
- // Ordering constants used to place the Action Mode or context menu items in their menu.
- private static final int MENU_ITEM_ORDER_ASSIST = 0;
- private static final int MENU_ITEM_ORDER_UNDO = 2;
- private static final int MENU_ITEM_ORDER_REDO = 3;
- private static final int MENU_ITEM_ORDER_CUT = 4;
- private static final int MENU_ITEM_ORDER_COPY = 5;
- private static final int MENU_ITEM_ORDER_PASTE = 6;
- private static final int MENU_ITEM_ORDER_SHARE = 7;
- private static final int MENU_ITEM_ORDER_SELECT_ALL = 8;
- private static final int MENU_ITEM_ORDER_REPLACE = 9;
- private static final int MENU_ITEM_ORDER_AUTOFILL = 10;
- private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 11;
- private static final int MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START = 50;
- private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100;
+ // Ordering constants used to place the Action Mode items in their menu.
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_ASSIST = 0;
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_CUT = 4;
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_COPY = 5;
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_PASTE = 6;
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_SHARE = 7;
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_SELECT_ALL = 8;
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_REPLACE = 9;
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_AUTOFILL = 10;
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 11;
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START = 50;
+ private static final int ACTION_MODE_MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100;
+
+ // Ordering constants used to place the Context Menu items in their menu.
+ private static final int CONTEXT_MENU_ITEM_ORDER_UNDO = 2;
+ private static final int CONTEXT_MENU_ITEM_ORDER_REDO = 3;
+ private static final int CONTEXT_MENU_ITEM_ORDER_CUT = 4;
+ private static final int CONTEXT_MENU_ITEM_ORDER_COPY = 5;
+ private static final int CONTEXT_MENU_ITEM_ORDER_PASTE = 6;
+ private static final int CONTEXT_MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 7;
+ private static final int CONTEXT_MENU_ITEM_ORDER_SELECT_ALL = 8;
+ private static final int CONTEXT_MENU_ITEM_ORDER_SHARE = 9;
+ private static final int CONTEXT_MENU_ITEM_ORDER_AUTOFILL = 10;
+ private static final int CONTEXT_MENU_ITEM_ORDER_REPLACE = 11;
+
+ private static final int CONTEXT_MENU_GROUP_UNDO_REDO = Menu.FIRST;
+ private static final int CONTEXT_MENU_GROUP_CLIPBOARD = Menu.FIRST + 1;
+ private static final int CONTEXT_MENU_GROUP_MISC = Menu.FIRST + 2;
private static final int FLAG_MISSPELLED_OR_GRAMMAR_ERROR =
SuggestionSpan.FLAG_MISSPELLED | SuggestionSpan.FLAG_GRAMMAR_ERROR;
@@ -3112,8 +3128,8 @@
for (int i = 0; i < suggestionInfoArray.length; i++) {
suggestionInfoArray[i] = new SuggestionInfo();
}
- final SubMenu subMenu = menu.addSubMenu(Menu.NONE, Menu.NONE, MENU_ITEM_ORDER_REPLACE,
- com.android.internal.R.string.replace);
+ final SubMenu subMenu = menu.addSubMenu(Menu.NONE, Menu.NONE,
+ CONTEXT_MENU_ITEM_ORDER_REPLACE, com.android.internal.R.string.replace);
final int numItems = mSuggestionHelper.getSuggestionInfo(suggestionInfoArray, null);
for (int i = 0; i < numItems; i++) {
final SuggestionInfo info = suggestionInfoArray[i];
@@ -3128,45 +3144,54 @@
}
}
- menu.add(Menu.NONE, TextView.ID_UNDO, MENU_ITEM_ORDER_UNDO,
+ final int keyboard = mTextView.getResources().getConfiguration().keyboard;
+ menu.setQwertyMode(keyboard == Configuration.KEYBOARD_QWERTY);
+
+ menu.setGroupDividerEnabled(true);
+
+ menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_UNDO, CONTEXT_MENU_ITEM_ORDER_UNDO,
com.android.internal.R.string.undo)
.setAlphabeticShortcut('z')
.setOnMenuItemClickListener(mOnContextMenuItemClickListener)
.setEnabled(mTextView.canUndo());
- menu.add(Menu.NONE, TextView.ID_REDO, MENU_ITEM_ORDER_REDO,
+ menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_REDO, CONTEXT_MENU_ITEM_ORDER_REDO,
com.android.internal.R.string.redo)
+ .setAlphabeticShortcut('z', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)
.setOnMenuItemClickListener(mOnContextMenuItemClickListener)
.setEnabled(mTextView.canRedo());
- menu.add(Menu.NONE, TextView.ID_CUT, MENU_ITEM_ORDER_CUT,
+ menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_CUT, CONTEXT_MENU_ITEM_ORDER_CUT,
com.android.internal.R.string.cut)
.setAlphabeticShortcut('x')
.setOnMenuItemClickListener(mOnContextMenuItemClickListener)
.setEnabled(mTextView.canCut());
- menu.add(Menu.NONE, TextView.ID_COPY, MENU_ITEM_ORDER_COPY,
+ menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_COPY, CONTEXT_MENU_ITEM_ORDER_COPY,
com.android.internal.R.string.copy)
.setAlphabeticShortcut('c')
.setOnMenuItemClickListener(mOnContextMenuItemClickListener)
.setEnabled(mTextView.canCopy());
- menu.add(Menu.NONE, TextView.ID_PASTE, MENU_ITEM_ORDER_PASTE,
+ menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE, CONTEXT_MENU_ITEM_ORDER_PASTE,
com.android.internal.R.string.paste)
.setAlphabeticShortcut('v')
.setEnabled(mTextView.canPaste())
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
- menu.add(Menu.NONE, TextView.ID_PASTE_AS_PLAIN_TEXT, MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT,
+ menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE_AS_PLAIN_TEXT,
+ CONTEXT_MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT,
com.android.internal.R.string.paste_as_plain_text)
+ .setAlphabeticShortcut('v', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)
.setEnabled(mTextView.canPasteAsPlainText())
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
- menu.add(Menu.NONE, TextView.ID_SHARE, MENU_ITEM_ORDER_SHARE,
- com.android.internal.R.string.share)
- .setEnabled(mTextView.canShare())
- .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
- menu.add(Menu.NONE, TextView.ID_SELECT_ALL, MENU_ITEM_ORDER_SELECT_ALL,
- com.android.internal.R.string.selectAll)
+ menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_SELECT_ALL,
+ CONTEXT_MENU_ITEM_ORDER_SELECT_ALL, com.android.internal.R.string.selectAll)
.setAlphabeticShortcut('a')
.setEnabled(mTextView.canSelectAllText())
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
- menu.add(Menu.NONE, TextView.ID_AUTOFILL, MENU_ITEM_ORDER_AUTOFILL,
+
+ menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_SHARE, CONTEXT_MENU_ITEM_ORDER_SHARE,
+ com.android.internal.R.string.share)
+ .setEnabled(mTextView.canShare())
+ .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
+ menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_AUTOFILL, CONTEXT_MENU_ITEM_ORDER_AUTOFILL,
android.R.string.autofill)
.setEnabled(mTextView.canRequestAutofill())
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
@@ -4348,28 +4373,28 @@
private void populateMenuWithItems(Menu menu) {
if (mTextView.canCut()) {
- menu.add(Menu.NONE, TextView.ID_CUT, MENU_ITEM_ORDER_CUT,
+ menu.add(Menu.NONE, TextView.ID_CUT, ACTION_MODE_MENU_ITEM_ORDER_CUT,
com.android.internal.R.string.cut)
.setAlphabeticShortcut('x')
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
if (mTextView.canCopy()) {
- menu.add(Menu.NONE, TextView.ID_COPY, MENU_ITEM_ORDER_COPY,
+ menu.add(Menu.NONE, TextView.ID_COPY, ACTION_MODE_MENU_ITEM_ORDER_COPY,
com.android.internal.R.string.copy)
.setAlphabeticShortcut('c')
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
if (mTextView.canPaste()) {
- menu.add(Menu.NONE, TextView.ID_PASTE, MENU_ITEM_ORDER_PASTE,
+ menu.add(Menu.NONE, TextView.ID_PASTE, ACTION_MODE_MENU_ITEM_ORDER_PASTE,
com.android.internal.R.string.paste)
.setAlphabeticShortcut('v')
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
if (mTextView.canShare()) {
- menu.add(Menu.NONE, TextView.ID_SHARE, MENU_ITEM_ORDER_SHARE,
+ menu.add(Menu.NONE, TextView.ID_SHARE, ACTION_MODE_MENU_ITEM_ORDER_SHARE,
com.android.internal.R.string.share)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
}
@@ -4377,7 +4402,7 @@
if (mTextView.canRequestAutofill()) {
final String selected = mTextView.getSelectedText();
if (selected == null || selected.isEmpty()) {
- menu.add(Menu.NONE, TextView.ID_AUTOFILL, MENU_ITEM_ORDER_AUTOFILL,
+ menu.add(Menu.NONE, TextView.ID_AUTOFILL, ACTION_MODE_MENU_ITEM_ORDER_AUTOFILL,
com.android.internal.R.string.autofill)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
}
@@ -4387,7 +4412,7 @@
menu.add(
Menu.NONE,
TextView.ID_PASTE_AS_PLAIN_TEXT,
- MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT,
+ ACTION_MODE_MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT,
com.android.internal.R.string.paste_as_plain_text)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
}
@@ -4414,7 +4439,7 @@
boolean canSelectAll = mTextView.canSelectAllText();
boolean selectAllItemExists = menu.findItem(TextView.ID_SELECT_ALL) != null;
if (canSelectAll && !selectAllItemExists) {
- menu.add(Menu.NONE, TextView.ID_SELECT_ALL, MENU_ITEM_ORDER_SELECT_ALL,
+ menu.add(Menu.NONE, TextView.ID_SELECT_ALL, ACTION_MODE_MENU_ITEM_ORDER_SELECT_ALL,
com.android.internal.R.string.selectAll)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
} else if (!canSelectAll && selectAllItemExists) {
@@ -4426,7 +4451,7 @@
boolean canReplace = mTextView.isSuggestionsEnabled() && shouldOfferToShowSuggestions();
boolean replaceItemExists = menu.findItem(TextView.ID_REPLACE) != null;
if (canReplace && !replaceItemExists) {
- menu.add(Menu.NONE, TextView.ID_REPLACE, MENU_ITEM_ORDER_REPLACE,
+ menu.add(Menu.NONE, TextView.ID_REPLACE, ACTION_MODE_MENU_ITEM_ORDER_REPLACE,
com.android.internal.R.string.replace)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
} else if (!canReplace && replaceItemExists) {
@@ -4452,12 +4477,12 @@
// Primary assist action (Always shown).
final MenuItem item = addAssistMenuItem(menu,
textClassification.getActions().get(0), TextView.ID_ASSIST,
- MENU_ITEM_ORDER_ASSIST, MenuItem.SHOW_AS_ACTION_ALWAYS);
+ ACTION_MODE_MENU_ITEM_ORDER_ASSIST, MenuItem.SHOW_AS_ACTION_ALWAYS);
item.setIntent(textClassification.getIntent());
} else if (hasLegacyAssistItem(textClassification)) {
// Legacy primary assist action (Always shown).
final MenuItem item = menu.add(
- TextView.ID_ASSIST, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST,
+ TextView.ID_ASSIST, TextView.ID_ASSIST, ACTION_MODE_MENU_ITEM_ORDER_ASSIST,
textClassification.getLabel())
.setIcon(textClassification.getIcon())
.setIntent(textClassification.getIntent());
@@ -4471,7 +4496,7 @@
for (int i = 1; i < count; i++) {
// Secondary assist action (Never shown).
addAssistMenuItem(menu, textClassification.getActions().get(i), Menu.NONE,
- MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START + i - 1,
+ ACTION_MODE_MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START + i - 1,
MenuItem.SHOW_AS_ACTION_NEVER);
}
mPrevTextClassification = textClassification;
@@ -7916,7 +7941,7 @@
for (int i = 0; i < size; i++) {
final ResolveInfo resolveInfo = mSupportedActivities.get(i);
menu.add(Menu.NONE, Menu.NONE,
- Editor.MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START + i,
+ Editor.ACTION_MODE_MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START + i,
getLabel(resolveInfo))
.setIntent(createProcessTextIntentForResolveInfo(resolveInfo))
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 089545a..be58893 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -108,6 +108,7 @@
import android.text.InputFilter;
import android.text.InputType;
import android.text.Layout;
+import android.text.NoCopySpan;
import android.text.ParcelableSpan;
import android.text.PrecomputedText;
import android.text.SegmentFinder;
@@ -905,6 +906,8 @@
private Scroller mScroller;
private TextPaint mTempTextPaint;
+ private Object mTempCursor;
+
@UnsupportedAppUsage
private BoringLayout.Metrics mBoring;
@UnsupportedAppUsage
@@ -10060,10 +10063,8 @@
}
int offset = mLayout.getOffsetForHorizontal(line, point.x);
String textToInsert = gesture.getTextToInsert();
- getEditableText().insert(offset, textToInsert);
- Selection.setSelection(getEditableText(), offset + textToInsert.length());
+ return tryInsertTextForHandwritingGesture(offset, textToInsert, gesture);
// TODO(b/243980426): Insert extra spaces if necessary.
- return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
}
/** @hide */
@@ -10163,12 +10164,11 @@
if (startOffset < endOffset) {
getEditableText().delete(startOffset, endOffset);
Selection.setSelection(getEditableText(), startOffset);
+ return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
} else {
// No whitespace found, so insert a space.
- getEditableText().insert(startOffset, " ");
- Selection.setSelection(getEditableText(), startOffset + 1);
+ return tryInsertTextForHandwritingGesture(startOffset, " ", gesture);
}
- return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
}
/** @hide */
@@ -10252,6 +10252,32 @@
area, segmentFinder, Layout.INCLUSION_STRATEGY_CONTAINS_CENTER);
}
+ private int tryInsertTextForHandwritingGesture(
+ int offset, String textToInsert, HandwritingGesture gesture) {
+ // A temporary cursor span is placed at the insertion offset. The span will be pushed
+ // forward when text is inserted, then the real cursor can be placed after the inserted
+ // text. A temporary cursor span is used in order to avoid modifying the real selection span
+ // in the case that the text is filtered out.
+ Editable editableText = getEditableText();
+ if (mTempCursor == null) {
+ mTempCursor = new NoCopySpan.Concrete();
+ }
+ editableText.setSpan(mTempCursor, offset, offset, Spanned.SPAN_POINT_POINT);
+
+ editableText.insert(offset, textToInsert);
+
+ int newOffset = editableText.getSpanStart(mTempCursor);
+ editableText.removeSpan(mTempCursor);
+ if (newOffset == offset) {
+ // The inserted text was filtered out.
+ return handleGestureFailure(gesture);
+ } else {
+ // Place the cursor after the inserted text.
+ Selection.setSelection(editableText, newOffset);
+ return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
+ }
+ }
+
private Pattern getWhitespacePattern() {
if (mWhitespacePattern == null) {
mWhitespacePattern = Pattern.compile("\\s+");
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index 2e55041..78de954 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -53,7 +53,7 @@
* see the <a href="https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/window/SurfaceSyncGroup.md">SurfaceSyncGroup documentation</a>
* </p>
*/
-public class SurfaceSyncGroup {
+public final class SurfaceSyncGroup {
private static final String TAG = "SurfaceSyncGroup";
private static final boolean DEBUG = false;
@@ -101,6 +101,9 @@
*/
public final ISurfaceSyncGroup mISurfaceSyncGroup = new ISurfaceSyncGroupImpl();
+ @GuardedBy("mLock")
+ private Runnable mAddedToSyncListener;
+
/**
* Token to identify this SurfaceSyncGroup. This is used to register the SurfaceSyncGroup in
* WindowManager. This token is also sent to other processes' SurfaceSyncGroup that want to be
@@ -447,12 +450,15 @@
}
/**
- * Invoked when the SurfaceSyncGroup has been added to another SurfaceSyncGroup and is ready
- * to proceed.
+ * Add a Runnable to be invoked when the SurfaceSyncGroup has been added to another
+ * SurfaceSyncGroup. This is useful to know when it's safe to proceed rendering.
*
* @hide
*/
- public void onSyncReady() {
+ public void setAddedToSyncListener(Runnable addedToSyncListener) {
+ synchronized (mLock) {
+ mAddedToSyncListener = addedToSyncListener;
+ }
}
private boolean addSyncToWm(IBinder token, boolean parentSyncGroupMerge,
@@ -525,12 +531,15 @@
if (DEBUG) {
Log.d(TAG, "setTransactionCallbackFromParent " + mName);
}
- boolean finished = false;
+
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW,
"setTransactionCallbackFromParent " + mName + " callback="
+ transactionReadyCallback.hashCode());
}
+
+ boolean finished = false;
+ Runnable addedToSyncListener = null;
synchronized (mLock) {
if (mFinished) {
finished = true;
@@ -577,6 +586,7 @@
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
};
+ addedToSyncListener = mAddedToSyncListener;
}
}
@@ -587,8 +597,8 @@
transactionReadyCallback.onTransactionReady(null);
} catch (RemoteException e) {
}
- } else {
- onSyncReady();
+ } else if (addedToSyncListener != null) {
+ addedToSyncListener.run();
}
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index 11bd47d..3478b0f 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -56,7 +56,7 @@
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);
+ "persist.wm.debug.win_metrics_lazy_insets", true);
private final Context mContext;
public WindowMetricsController(@NonNull Context context) {
@@ -127,7 +127,7 @@
isScreenRound, alwaysConsumeSystemBars, SOFT_INPUT_ADJUST_NOTHING,
0 /* flags */, SYSTEM_UI_FLAG_VISIBLE,
WindowManager.LayoutParams.INVALID_WINDOW_TYPE, windowingMode,
- null /* typeSideMap */);
+ null /* idSideMap */);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/com/android/internal/app/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java
index f843318..fc5cb4b 100644
--- a/core/java/com/android/internal/app/AssistUtils.java
+++ b/core/java/com/android/internal/app/AssistUtils.java
@@ -229,6 +229,35 @@
}
}
+ /**
+ * Enables visual detection service.
+ *
+ * @param listener to receive visual attention gained/lost events.
+ */
+ public void enableVisualQueryDetection(
+ IVisualQueryDetectionAttentionListener listener) {
+ try {
+ if (mVoiceInteractionManagerService != null) {
+ mVoiceInteractionManagerService.enableVisualQueryDetection(listener);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to register visual query detection attention listener", e);
+ }
+ }
+
+ /**
+ * Disables visual query detection.
+ */
+ public void disableVisualQueryDetection() {
+ try {
+ if (mVoiceInteractionManagerService != null) {
+ mVoiceInteractionManagerService.disableVisualQueryDetection();
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to register visual query detection attention listener", e);
+ }
+ }
+
@UnsupportedAppUsage
public ComponentName getAssistComponentForUser(int userId) {
final String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(),
diff --git a/core/java/com/android/internal/app/ISoundTriggerSession.aidl b/core/java/com/android/internal/app/ISoundTriggerSession.aidl
index a1ba558..3269ffc 100644
--- a/core/java/com/android/internal/app/ISoundTriggerSession.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerSession.aidl
@@ -36,7 +36,8 @@
void deleteSoundModel(in ParcelUuid soundModelId);
- int startRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback,
+ int startRecognition(in SoundTrigger.GenericSoundModel soundModel,
+ in IRecognitionStatusCallback callback,
in SoundTrigger.RecognitionConfig config, boolean runInBatterySaver);
int stopRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
diff --git a/core/java/com/android/internal/app/IVisualQueryDetectionAttentionListener.aidl b/core/java/com/android/internal/app/IVisualQueryDetectionAttentionListener.aidl
new file mode 100644
index 0000000..3e48da7
--- /dev/null
+++ b/core/java/com/android/internal/app/IVisualQueryDetectionAttentionListener.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+/**
+ * Allows sysui to notify users the assistant is ready to take a query without notifying the
+ * assistant app.
+ */
+oneway interface IVisualQueryDetectionAttentionListener {
+ /**
+ * Called when attention signal is sent.
+ */
+ void onAttentionGained();
+
+ /**
+ * Called when a attention signal is lost.
+ */
+ void onAttentionLost();
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 88d6425..619b420 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -39,6 +39,7 @@
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.app.IVisualQueryDetectionAttentionListener;
interface IVoiceInteractionManagerService {
void showSession(in Bundle sessionArgs, int flags, String attributionTag);
@@ -300,6 +301,12 @@
*/
void shutdownHotwordDetectionService();
+ @EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
+ void enableVisualQueryDetection(in IVisualQueryDetectionAttentionListener Listener);
+
+ @EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
+ void disableVisualQueryDetection();
+
void startPerceiving(in IVisualQueryDetectionVoiceInteractionCallback callback);
void stopPerceiving();
diff --git a/core/java/com/android/internal/app/OWNERS b/core/java/com/android/internal/app/OWNERS
index 56da8e0..69660ae 100644
--- a/core/java/com/android/internal/app/OWNERS
+++ b/core/java/com/android/internal/app/OWNERS
@@ -11,6 +11,7 @@
per-file *Assist* = file:/core/java/android/service/voice/OWNERS
per-file *Hotword* = file:/core/java/android/service/voice/OWNERS
per-file *Voice* = file:/core/java/android/service/voice/OWNERS
+per-file *VisualQuery* = file:/core/java/android/service/voice/OWNERS
# System language settings
per-file *Locale* = file:platform/packages/apps/Settings:/src/com/android/settings/localepicker/OWNERS
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index b9373be..1084c71 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -562,6 +562,13 @@
"task_manager_show_user_visible_jobs";
/**
+ * (boolean) Whether the task manager should tell JobScheduler it's about to ask for an
+ * app stop.
+ */
+ public static final String TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP =
+ "task_manager_inform_job_scheduler_of_pending_app_stop";
+
+ /**
* (boolean) Whether to show notification volume control slider separate from ring.
*/
public static final String VOLUME_SEPARATE_NOTIFICATION = "volume_separate_notification";
diff --git a/core/java/com/android/internal/statusbar/AppClipsServiceConnector.java b/core/java/com/android/internal/statusbar/AppClipsServiceConnector.java
new file mode 100644
index 0000000..287b85f
--- /dev/null
+++ b/core/java/com/android/internal/statusbar/AppClipsServiceConnector.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.statusbar;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * A helper class to communicate with the App Clips service running in SystemUI.
+ */
+public class AppClipsServiceConnector {
+
+ private static final String TAG = AppClipsServiceConnector.class.getSimpleName();
+
+ private final Context mContext;
+ private final Handler mHandler;
+
+ public AppClipsServiceConnector(Context context) {
+ mContext = context;
+ HandlerThread handlerThread = new HandlerThread(TAG);
+ handlerThread.start();
+ mHandler = handlerThread.getThreadHandler();
+ }
+
+ /**
+ * @return true if the task represented by {@code taskId} can launch App Clips screenshot flow,
+ * false otherwise.
+ */
+ public boolean canLaunchCaptureContentActivityForNote(int taskId) {
+ try {
+ CompletableFuture<Boolean> future = new CompletableFuture<>();
+ connectToServiceAndProcessRequest(taskId, future);
+ return future.get();
+ } catch (Exception e) {
+ Log.d(TAG, "Exception from service\n" + e);
+ }
+
+ return false;
+ }
+
+ private void connectToServiceAndProcessRequest(int taskId, CompletableFuture<Boolean> future) {
+ ServiceConnection serviceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ try {
+ future.complete(IAppClipsService.Stub.asInterface(
+ service).canLaunchCaptureContentActivityForNote(taskId));
+ } catch (Exception e) {
+ Log.d(TAG, "Exception from service\n" + e);
+ }
+ future.complete(false);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (!future.isDone()) {
+ future.complete(false);
+ }
+ }
+ };
+
+ final ComponentName serviceComponent = ComponentName.unflattenFromString(
+ mContext.getResources().getString(
+ com.android.internal.R.string.config_screenshotAppClipsServiceComponent));
+ final Intent serviceIntent = new Intent();
+ serviceIntent.setComponent(serviceComponent);
+
+ boolean bindService = mContext.bindServiceAsUser(serviceIntent, serviceConnection,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, mHandler,
+ mContext.getUser());
+
+ // Complete the future early if service not bound.
+ if (!bindService) {
+ future.complete(false);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/statusbar/IAppClipsService.aidl b/core/java/com/android/internal/statusbar/IAppClipsService.aidl
new file mode 100644
index 0000000..013d0d3
--- /dev/null
+++ b/core/java/com/android/internal/statusbar/IAppClipsService.aidl
@@ -0,0 +1,26 @@
+/**
+ * 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.statusbar;
+
+/**
+ * A service that runs in SystemUI and helps determine if App Clips flow is supported in the
+ * current state of device. This service needs to run in SystemUI in order to communicate with the
+ * instance of app bubbles.
+ */
+interface IAppClipsService {
+ boolean canLaunchCaptureContentActivityForNote(in int taskId);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/util/ScreenshotRequest.java b/core/java/com/android/internal/util/ScreenshotRequest.java
index 1902f80..c8b7def 100644
--- a/core/java/com/android/internal/util/ScreenshotRequest.java
+++ b/core/java/com/android/internal/util/ScreenshotRequest.java
@@ -173,6 +173,9 @@
public Builder(
@WindowManager.ScreenshotType int type,
@WindowManager.ScreenshotSource int source) {
+ if (type != TAKE_SCREENSHOT_FULLSCREEN && type != TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+ throw new IllegalArgumentException("Invalid screenshot type requested!");
+ }
mType = type;
mSource = source;
}
diff --git a/core/java/com/android/server/backup/ShortcutBackupHelper.java b/core/java/com/android/server/backup/ShortcutBackupHelper.java
index 0b3f2ae..3c4b622 100644
--- a/core/java/com/android/server/backup/ShortcutBackupHelper.java
+++ b/core/java/com/android/server/backup/ShortcutBackupHelper.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.content.pm.IShortcutService;
import android.os.ServiceManager;
-import android.os.UserHandle;
import android.util.Slog;
public class ShortcutBackupHelper extends BlobBackupHelper {
@@ -28,8 +27,11 @@
private static final String KEY_USER_FILE = "shortcutuser.xml";
- public ShortcutBackupHelper() {
+ private final int mUserId;
+
+ public ShortcutBackupHelper(int userId) {
super(BLOB_VERSION, KEY_USER_FILE);
+ mUserId = userId;
}
private IShortcutService getShortcutService() {
@@ -42,7 +44,7 @@
switch (key) {
case KEY_USER_FILE:
try {
- return getShortcutService().getBackupPayload(UserHandle.USER_SYSTEM);
+ return getShortcutService().getBackupPayload(mUserId);
} catch (Exception e) {
Slog.wtf(TAG, "Backup failed", e);
}
@@ -58,7 +60,7 @@
switch (key) {
case KEY_USER_FILE:
try {
- getShortcutService().applyRestore(payload, UserHandle.USER_SYSTEM);
+ getShortcutService().applyRestore(payload, mUserId);
} catch (Exception e) {
Slog.wtf(TAG, "Restore failed", e);
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 82f414b..b79c540 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -40,6 +40,12 @@
cppflags: ["-Wno-conversion-null"],
+ product_variables: {
+ eng: {
+ cflags: ["-DNO_RESET_STACK_PROTECTOR"],
+ },
+ },
+
cpp_std: "gnu++20",
srcs: [
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 6a92581..5965e17 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -2302,7 +2302,7 @@
setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MIN);
}
-#if defined(__BIONIC__)
+#if defined(__BIONIC__) && !defined(NO_RESET_STACK_PROTECTOR)
// Reset the stack guard for the new process.
android_reset_stack_guards();
#endif
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index fd0d9c5..128de8b 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -88,6 +88,9 @@
optional SettingProto assisted_gps_enabled = 14 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto audio_safe_volume_state = 15 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto audio_safe_csd_current_value = 157 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto audio_safe_csd_next_warning = 158 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto audio_safe_csd_dose_records = 159 [ (android.privacy).dest = DEST_AUTOMATIC ];
reserved 17; // Used to be autofill_compat_mode_allowed_packages
@@ -1087,5 +1090,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 157;
+ // Next tag = 160;
}
diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto
index 607fd10..c98e346 100644
--- a/core/proto/android/service/usb.proto
+++ b/core/proto/android/service/usb.proto
@@ -284,7 +284,8 @@
optional int32 cards_parser = 1;
repeated UsbAlsaDeviceProto alsa_devices = 2;
- repeated UsbMidiDeviceProto midi_devices = 3;
+ reserved 3; // previously midi_devices, now unused
+ repeated UsbAlsaMidiDeviceProto alsa_midi_devices = 4;
}
message UsbAlsaDeviceProto {
@@ -299,7 +300,7 @@
optional string address = 6;
}
-message UsbMidiDeviceProto {
+message UsbAlsaMidiDeviceProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
optional int32 card = 1;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 49f4d35..6aee3cd 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2579,6 +2579,14 @@
<!-- ==================================================== -->
<eat-comment />
+ <!-- Allows an application to capture screen content to perform a screenshot using the intent
+ action {@link android.content.Intent#ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE}.
+ <p>Protection level: internal|role
+ <p>Intended for use by ROLE_NOTES only.
+ -->
+ <permission android:name="android.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE"
+ android:protectionLevel="internal|role" />
+
<!-- Allows an application to get notified when a screen capture of its windows is attempted.
<p>Protection level: normal
-->
@@ -6921,8 +6929,8 @@
<!-- Allows an assistive application to perform actions on behalf of users inside of
applications.
- <p>For now, this permission is only granted to system applications fulfilling the
- ASSISTANT role.
+ <p>For now, this permission is only granted to the Assistant application selected by
+ the user.
<p>Protection level: internal|role
-->
<permission android:name="android.permission.EXECUTE_APP_ACTION"
diff --git a/core/res/res/drawable/ic_clone_badge.xml b/core/res/res/drawable/ic_clone_badge.xml
new file mode 100644
index 0000000..1682425
--- /dev/null
+++ b/core/res/res/drawable/ic_clone_badge.xml
@@ -0,0 +1,29 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M22,9.5C22,13.642 18.642,17 14.5,17C10.358,17 7,13.642 7,9.5C7,5.358 10.358,2 14.5,2C18.642,2 22,5.358 22,9.5Z"
+ android:fillColor="@android:color/system_neutral2_800"/>
+ <path
+ android:pathData="M9.5,20.333C12.722,20.333 15.333,17.722 15.333,14.5C15.333,11.278 12.722,8.667 9.5,8.667C6.278,8.667 3.667,11.278 3.667,14.5C3.667,17.722 6.278,20.333 9.5,20.333ZM9.5,22C13.642,22 17,18.642 17,14.5C17,10.358 13.642,7 9.5,7C5.358,7 2,10.358 2,14.5C2,18.642 5.358,22 9.5,22Z"
+ android:fillColor="@android:color/system_neutral2_800"
+ android:fillType="evenOdd"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_clone_icon_badge.xml b/core/res/res/drawable/ic_clone_icon_badge.xml
new file mode 100644
index 0000000..e4eabc8
--- /dev/null
+++ b/core/res/res/drawable/ic_clone_icon_badge.xml
@@ -0,0 +1,35 @@
+<!--
+ ~ 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="64dp"
+ android:height="64dp"
+ android:viewportWidth="64"
+ android:viewportHeight="64">
+<group
+ android:scaleX=".66"
+ android:scaleY=".66"
+ android:translateX="42"
+ android:translateY="42">
+ <path
+ android:pathData="M22,9.5C22,13.642 18.642,17 14.5,17C10.358,17 7,13.642 7,9.5C7,5.358 10.358,2 14.5,2C18.642,2 22,5.358 22,9.5Z"
+ android:fillColor="@android:color/system_neutral2_800"/>
+ <path
+ android:pathData="M9.5,20.333C12.722,20.333 15.333,17.722 15.333,14.5C15.333,11.278 12.722,8.667 9.5,8.667C6.278,8.667 3.667,11.278 3.667,14.5C3.667,17.722 6.278,20.333 9.5,20.333ZM9.5,22C13.642,22 17,18.642 17,14.5C17,10.358 13.642,7 9.5,7C5.358,7 2,10.358 2,14.5C2,18.642 5.358,22 9.5,22Z"
+ android:fillColor="@android:color/system_neutral2_800"
+ android:fillType="evenOdd"/>
+</group>
+</vector>
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index 9e735d0..c3366cf 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -207,4 +207,25 @@
be set by OEMs for devices which use eUICCs. -->
<integer-array name="non_removable_euicc_slots"></integer-array>
+ <!-- Device state identifiers and strings for system notifications. The string arrays must have
+ the same length and order as the identifier array. -->
+ <integer-array name="device_state_notification_state_identifiers">
+ <!-- TODO(b/267231269) change to concurrent display identifier when ready -->
+ <item>-1</item>
+ </integer-array>
+ <string-array name="device_state_notification_names">
+ <item>@string/concurrent_display_notification_name</item>
+ </string-array>
+ <string-array name="device_state_notification_active_titles">
+ <item>@string/concurrent_display_notification_active_title</item>
+ </string-array>
+ <string-array name="device_state_notification_active_contents">
+ <item>@string/concurrent_display_notification_active_content</item>
+ </string-array>
+ <string-array name="device_state_notification_thermal_titles">
+ <item>@string/concurrent_display_notification_thermal_title</item>
+ </string-array>
+ <string-array name="device_state_notification_thermal_contents">
+ <item>@string/concurrent_display_notification_thermal_content</item>
+ </string-array>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b8ce7e3..0c13484 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2674,6 +2674,14 @@
<!-- Maximum velocity to initiate a fling, as measured in dips per second. -->
<dimen name="config_viewMaxFlingVelocity">8000dp</dimen>
+ <!-- Minimum velocity (absolute value) to initiate a fling from a rotary encoder device, as
+ measured in dips per second. Setting this to -1dp disables rotary encoder fling. -->
+ <dimen name="config_viewMinRotaryEncoderFlingVelocity">-1dp</dimen>
+
+ <!-- Maximum velocity (absolute value) to initiate a fling from a rotary encoder device, as
+ measured in dips per second. Setting this to -1dp disables rotary encoder fling. -->
+ <dimen name="config_viewMaxRotaryEncoderFlingVelocity">-1dp</dimen>
+
<!-- Amount of time in ms the user needs to press the relevant key to bring up the
global actions dialog -->
<integer name="config_globalActionsKeyTimeout">500</integer>
@@ -2735,6 +2743,11 @@
Should be false for most devices, except automotive vehicle with passenger displays. -->
<bool name="config_multiuserVisibleBackgroundUsers">false</bool>
+ <!-- Whether the device allows users to start in background visible on the default display.
+ Should be false for most devices, except passenger-only automotive build (i.e., when
+ Android runs in a separate system in the back seat to manage the passenger displays) -->
+ <bool name="config_multiuserVisibleBackgroundUsersOnDefaultDisplay">false</bool>
+
<!-- Whether to automatically switch to the designated Dock User (the user chosen for
displaying dreams, etc.) after a timeout when the device is docked. -->
<bool name="config_enableTimeoutToDockUserWhenDocked">false</bool>
@@ -3220,6 +3233,12 @@
<string name="config_somnambulatorComponent" translatable="false"
>com.android.systemui/com.android.systemui.Somnambulator</string>
+ <!-- The component name of the screenshot App Clips service that communicates with SystemUI to
+ evaluate certain aspects of App Clips flow such as whether a calling activity can launch
+ capture content for note activity. -->
+ <string name="config_screenshotAppClipsServiceComponent" translatable="false"
+ >com.android.systemui/com.android.systemui.screenshot.appclips.AppClipsService</string>
+
<!-- The component name of a special dock app that merely launches a dream.
We don't want to launch this app when docked because it causes an unnecessary
activity transition. We just want to start the dream.. -->
@@ -5001,6 +5020,26 @@
-->
</integer-array>
+ <!-- The properties for handling UDFPS touch detection
+ <string-array name="config_udfps_touch_detection_options">
+ <item>[detector_type],[sensor_shape],[target_size],[min_ellipse_overlap_percentage]</item>
+ </string-array>
+
+ [detector_type]: 0 for bounding box detector, 1 for ellipse detector
+ [sensor_shape]: 0 for square, 1 for circle
+ [target_size]: percentage (defined as a float of 0-1) of sensor that is considered the target, expands outward from center
+ [min_ellipse_overlap_percentage]: minimum percentage (float from 0-1) needed to be considered a valid overlap
+ [step_size]: size of each step when iterating over sensor pixel grid
+ -->
+ <string-array name="config_udfps_touch_detection_options">
+ <item>0,0,1.0,0,1</item>
+ <item>1,1,1.0,0,1</item>
+ <item>1,1,1.0,.4,1</item>
+ </string-array>
+
+ <!-- The integer index of the selected option in config_udfps_touch_detection_options -->
+ <integer name="config_selected_udfps_touch_detection">2</integer>
+
<!-- An array of arrays of side fingerprint sensor properties relative to each display.
Note: this value is temporary and is expected to be queried directly
from the HAL in the future. -->
@@ -6210,4 +6249,27 @@
trusted certificate using the SHA-256 digest algorithm. -->
<string-array name="config_healthConnectMigrationKnownSigners">
</string-array>
+
+ <!-- The Universal Stylus Initiative (USI) protocol version supported by each display.
+ (@see https://universalstylus.org/).
+
+ The i-th value in this array corresponds to the supported USI version of the i-th display
+ listed in config_displayUniqueIdArray. On a single-display device, the
+ config_displayUniqueIdArray may be empty, in which case the only value in this array should
+ be the USI version for the main built-in display.
+
+ If the display does not support USI, the version value should be an empty string. If the
+ display supports USI, the version must be in the following format:
+ "<major-version>.<minor-version>"
+
+ For example, "", "1.0", and "2.0" are valid values. -->
+ <string-array name="config_displayUsiVersionArray" translatable="false">
+ <item>""</item>
+ </string-array>
+
+ <!-- Whether system apps should be scanned in the stopped state during initial boot.
+ Packages can be added by OEMs in an allowlist, to prevent them from being scanned as
+ "stopped" during initial boot of a device, or after an OTA update. Stopped state of
+ an app is not changed during subsequent reboots. -->
+ <bool name="config_stopSystemPackagesByDefault">false</bool>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e330ee3..da44c2e 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5067,6 +5067,13 @@
<string name="managed_profile_label_badge_2">2nd Work <xliff:g id="label" example="Email">%1$s</xliff:g></string>
<string name="managed_profile_label_badge_3">3rd Work <xliff:g id="label" example="Email">%1$s</xliff:g></string>
+ <!--
+ Used to wrap a label for content description for a Clone profile, e.g. "Clone Messenger"
+ instead of Messenger when the Messenger app is cloned.
+ [CHAR LIMIT=20]
+ -->
+ <string name="clone_profile_label_badge">Clone <xliff:g id="label" example="Messenger">%1$s</xliff:g></string>
+
<!-- DO NOT TRANSLATE -->
<string name="time_placeholder">--</string>
@@ -6234,4 +6241,17 @@
<string name="mic_access_on_toast">Microphone is available</string>
<!-- Toast message that is shown when the user mutes the microphone from the keyboard. [CHAR LIMIT=TOAST] -->
<string name="mic_access_off_toast">Microphone is blocked</string>
+
+ <!-- Name of concurrent display notifications. [CHAR LIMIT=NONE] -->
+ <string name="concurrent_display_notification_name">Dual screen</string>
+ <!-- Title of concurrent display active notification. [CHAR LIMIT=NONE] -->
+ <string name="concurrent_display_notification_active_title">Dual screen is on</string>
+ <!-- Content of concurrent display active notification. [CHAR LIMIT=NONE] -->
+ <string name="concurrent_display_notification_active_content"><xliff:g id="app_name" example="Camera app">%1$s</xliff:g> is using both displays to show content</string>
+ <!-- Title of concurrent display thermal notification. [CHAR LIMIT=NONE] -->
+ <string name="concurrent_display_notification_thermal_title">Device is too warm</string>
+ <!-- Content of concurrent display thermal notification. [CHAR LIMIT=NONE] -->
+ <string name="concurrent_display_notification_thermal_content">Dual Screen is unavailable because your phone is getting too warm</string>
+ <!-- Text of device state notification turn off button. [CHAR LIMIT=NONE] -->
+ <string name="device_state_notification_turn_off_button">Turn off</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 76af814..18084d8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -370,6 +370,7 @@
<java-symbol type="string" name="config_controlsPackage" />
<java-symbol type="string" name="config_screenRecorderComponent" />
<java-symbol type="string" name="config_somnambulatorComponent" />
+ <java-symbol type="string" name="config_screenshotAppClipsServiceComponent" />
<java-symbol type="string" name="config_screenshotServiceComponent" />
<java-symbol type="string" name="config_screenshotErrorReceiverComponent" />
<java-symbol type="string" name="config_slicePermissionComponent" />
@@ -473,6 +474,7 @@
<java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
<java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
<java-symbol type="bool" name="config_multiuserVisibleBackgroundUsers" />
+ <java-symbol type="bool" name="config_multiuserVisibleBackgroundUsersOnDefaultDisplay" />
<java-symbol type="bool" name="config_enableTimeoutToDockUserWhenDocked" />
<java-symbol type="integer" name="config_userTypePackageWhitelistMode"/>
<java-symbol type="xml" name="config_user_types" />
@@ -514,6 +516,8 @@
<java-symbol type="dimen" name="config_ambiguousGestureMultiplier" />
<java-symbol type="dimen" name="config_viewMinFlingVelocity" />
<java-symbol type="dimen" name="config_viewMaxFlingVelocity" />
+ <java-symbol type="dimen" name="config_viewMinRotaryEncoderFlingVelocity" />
+ <java-symbol type="dimen" name="config_viewMaxRotaryEncoderFlingVelocity" />
<java-symbol type="dimen" name="config_scrollbarSize" />
<java-symbol type="dimen" name="config_horizontalScrollFactor" />
<java-symbol type="dimen" name="config_verticalScrollFactor" />
@@ -1063,6 +1067,7 @@
<java-symbol type="string" name="managed_profile_label_badge" />
<java-symbol type="string" name="managed_profile_label_badge_2" />
<java-symbol type="string" name="managed_profile_label_badge_3" />
+ <java-symbol type="string" name="clone_profile_label_badge" />
<java-symbol type="string" name="mediasize_unknown_portrait" />
<java-symbol type="string" name="mediasize_unknown_landscape" />
<java-symbol type="string" name="mediasize_iso_a0" />
@@ -1386,6 +1391,8 @@
<java-symbol type="drawable" name="ic_qs_auto_rotate" />
<java-symbol type="drawable" name="ic_qs_dnd" />
<java-symbol type="drawable" name="ic_qs_one_handed_mode" />
+ <java-symbol type="drawable" name="ic_clone_icon_badge" />
+ <java-symbol type="drawable" name="ic_clone_badge" />
<java-symbol type="drawable" name="sim_light_blue" />
<java-symbol type="drawable" name="sim_light_green" />
@@ -2494,6 +2501,8 @@
<java-symbol type="string" name="zen_mode_default_weekends_name" />
<java-symbol type="string" name="zen_mode_default_events_name" />
<java-symbol type="string" name="zen_mode_default_every_night_name" />
+ <java-symbol type="string" name="display_rotation_camera_compat_toast_after_rotation" />
+ <java-symbol type="string" name="display_rotation_camera_compat_toast_in_split_screen" />
<java-symbol type="array" name="config_system_condition_providers" />
<java-symbol type="string" name="muted_by" />
<java-symbol type="string" name="zen_mode_alarm" />
@@ -2646,6 +2655,8 @@
<java-symbol type="array" name="config_biometric_sensors" />
<java-symbol type="bool" name="allow_test_udfps" />
<java-symbol type="array" name="config_udfps_sensor_props" />
+ <java-symbol type="array" name="config_udfps_touch_detection_options" />
+ <java-symbol type="integer" name="config_selected_udfps_touch_detection" />
<java-symbol type="array" name="config_sfps_sensor_props" />
<java-symbol type="bool" name="config_is_powerbutton_fps" />
<java-symbol type="array" name="config_udfps_enroll_stage_thresholds" />
@@ -4874,6 +4885,18 @@
<java-symbol type="array" name="config_deviceStatesAvailableForAppRequests" />
<java-symbol type="array" name="config_serviceStateLocationAllowedPackages" />
<java-symbol type="integer" name="config_deviceStateRearDisplay"/>
+ <java-symbol type="array" name="device_state_notification_state_identifiers"/>
+ <java-symbol type="array" name="device_state_notification_names"/>
+ <java-symbol type="array" name="device_state_notification_active_titles"/>
+ <java-symbol type="array" name="device_state_notification_active_contents"/>
+ <java-symbol type="array" name="device_state_notification_thermal_titles"/>
+ <java-symbol type="array" name="device_state_notification_thermal_contents"/>
+ <java-symbol type="string" name="concurrent_display_notification_name"/>
+ <java-symbol type="string" name="concurrent_display_notification_active_title"/>
+ <java-symbol type="string" name="concurrent_display_notification_active_content"/>
+ <java-symbol type="string" name="concurrent_display_notification_thermal_title"/>
+ <java-symbol type="string" name="concurrent_display_notification_thermal_content"/>
+ <java-symbol type="string" name="device_state_notification_turn_off_button"/>
<java-symbol type="bool" name="config_independentLockscreenLiveWallpaper"/>
<!-- For app language picker -->
@@ -4892,4 +4915,6 @@
<java-symbol type="string" name="config_mainDisplayShape"/>
<java-symbol type="string" name="config_secondaryDisplayShape"/>
<java-symbol type="array" name="config_displayShapeArray" />
+
+ <java-symbol type="bool" name="config_stopSystemPackagesByDefault"/>
</resources>
diff --git a/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
index b1991c2..b3c7f88 100644
--- a/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
+++ b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
@@ -60,8 +60,8 @@
public void timeReadWriteInsetsState(int reps) {
final InsetsState insetsState = new InsetsState();
- for (int i = 0; i < InsetsState.SIZE; i++) {
- insetsState.addSource(new InsetsSource(i, InsetsState.toPublicType(i)));
+ for (int i = 0; i < 10; i++) {
+ insetsState.addSource(new InsetsSource(i, 1 << i));
}
for (int i = 0; i < reps; i++) {
insetsState.writeToParcel(mParcel, 0);
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 4548730..34d669b 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -31,6 +31,7 @@
import static android.app.Notification.EXTRA_MEDIA_REMOTE_INTENT;
import static android.app.Notification.EXTRA_MEDIA_SESSION;
import static android.app.Notification.EXTRA_MESSAGING_PERSON;
+import static android.app.Notification.EXTRA_PEOPLE_LIST;
import static android.app.Notification.EXTRA_PICTURE;
import static android.app.Notification.EXTRA_PICTURE_ICON;
import static android.app.Notification.MessagingStyle.Message.KEY_DATA_URI;
@@ -817,6 +818,23 @@
}
@Test
+ public void testRecoverBuilder_nullExtraPeopleList_noCrash() {
+ Bundle extras = new Bundle();
+ extras.putParcelable(EXTRA_PEOPLE_LIST, null);
+
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(0)
+ .addExtras(extras)
+ .build();
+ Bundle fakeTypes = new Bundle();
+ fakeTypes.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, new Bundle());
+
+ Notification.Builder.recoverBuilder(mContext, n);
+
+ // no crash, good
+ }
+
+ @Test
public void testVisitUris_invalidExtra_noCrash() {
Notification n = new Notification.Builder(mContext, "test")
.setSmallIcon(0)
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
index 8bed31f..6636cbd 100644
--- a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
@@ -44,10 +44,12 @@
public void testEquals() {
TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setUseLocationEnabled(true)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setSetManualTimeZoneCapability(CAPABILITY_POSSESSED);
TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setUseLocationEnabled(true)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setSetManualTimeZoneCapability(CAPABILITY_POSSESSED);
{
@@ -70,6 +72,20 @@
assertEquals(one, two);
}
+ builder2.setUseLocationEnabled(false);
+ {
+ TimeZoneCapabilities one = builder1.build();
+ TimeZoneCapabilities two = builder2.build();
+ assertNotEquals(one, two);
+ }
+
+ builder1.setUseLocationEnabled(false);
+ {
+ TimeZoneCapabilities one = builder1.build();
+ TimeZoneCapabilities two = builder2.build();
+ assertEquals(one, two);
+ }
+
builder2.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
{
TimeZoneCapabilities one = builder1.build();
@@ -103,6 +119,7 @@
public void testParcelable() {
TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setUseLocationEnabled(true)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setSetManualTimeZoneCapability(CAPABILITY_POSSESSED);
assertRoundTripParcelable(builder.build());
@@ -110,6 +127,9 @@
builder.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
assertRoundTripParcelable(builder.build());
+ builder.setUseLocationEnabled(false);
+ assertRoundTripParcelable(builder.build());
+
builder.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
assertRoundTripParcelable(builder.build());
@@ -126,6 +146,7 @@
.build();
TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setUseLocationEnabled(true)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
.build();
@@ -149,6 +170,7 @@
.build();
TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+ .setUseLocationEnabled(true)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
.build();
@@ -164,6 +186,7 @@
public void copyBuilder_copiesAllFields() {
TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+ .setUseLocationEnabled(true)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
.build();
@@ -176,6 +199,24 @@
TimeZoneCapabilities expectedCapabilities =
new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setUseLocationEnabled(true)
+ .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+ .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+ .build();
+
+ assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
+ }
+
+ {
+ TimeZoneCapabilities updatedCapabilities =
+ new TimeZoneCapabilities.Builder(capabilities)
+ .setUseLocationEnabled(false)
+ .build();
+
+ TimeZoneCapabilities expectedCapabilities =
+ new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
+ .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+ .setUseLocationEnabled(false)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
.build();
@@ -192,6 +233,7 @@
TimeZoneCapabilities expectedCapabilities =
new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+ .setUseLocationEnabled(true)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
.build();
@@ -208,6 +250,7 @@
TimeZoneCapabilities expectedCapabilities =
new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+ .setUseLocationEnabled(true)
.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
.setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
.build();
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 958fdc6..a3eda8d 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -17,7 +17,7 @@
package android.view;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -92,7 +92,7 @@
@Test
public void testImeVisibility() {
- final InsetsSourceControl ime = new InsetsSourceControl(ITYPE_IME, WindowInsets.Type.ime(),
+ final InsetsSourceControl ime = new InsetsSourceControl(ID_IME, WindowInsets.Type.ime(),
mLeash, false, new Point(), Insets.NONE);
mController.onControlsChanged(new InsetsSourceControl[] { ime });
@@ -121,7 +121,7 @@
mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
// set control and verify visibility is applied.
- InsetsSourceControl control = new InsetsSourceControl(ITYPE_IME,
+ InsetsSourceControl control = new InsetsSourceControl(ID_IME,
WindowInsets.Type.ime(), mLeash, false, new Point(), Insets.NONE);
mController.onControlsChanged(new InsetsSourceControl[] { control });
// IME show animation should be triggered when control becomes available.
@@ -161,7 +161,7 @@
}
// set control and verify visibility is applied.
- InsetsSourceControl control = Mockito.spy(new InsetsSourceControl(ITYPE_IME,
+ InsetsSourceControl control = Mockito.spy(new InsetsSourceControl(ID_IME,
WindowInsets.Type.ime(), mLeash, false, new Point(), Insets.NONE));
// Simulate IME source control set this flag when the target has starting window.
control.setSkipAnimationOnce(true);
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index cc5f7f8..1682135 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -16,8 +16,8 @@
package android.view;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowInsets.Type.systemBars;
import static org.junit.Assert.assertEquals;
@@ -66,6 +66,11 @@
@RunWith(AndroidJUnit4.class)
public class InsetsAnimationControlImplTest {
+ private static final int ID_STATUS_BAR = InsetsSource.createId(
+ null /* owner */, 0 /* index */, statusBars());
+ private static final int ID_NAVIGATION_BAR = InsetsSource.createId(
+ null /* owner */, 0 /* index */, navigationBars());
+
private InsetsAnimationControlImpl mController;
private SurfaceSession mSession = new SurfaceSession();
@@ -87,29 +92,31 @@
.setName("testSurface")
.build();
mInsetsState = new InsetsState();
- mInsetsState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 500, 100));
- mInsetsState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(400, 0, 500, 500));
- InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR,
+ mInsetsState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 500, 100));
+ mInsetsState.getOrCreateSource(ID_NAVIGATION_BAR, navigationBars())
+ .setFrame(new Rect(400, 0, 500, 500));
+ InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(ID_STATUS_BAR,
WindowInsets.Type.statusBars(), mInsetsState,
() -> mMockTransaction, mMockController);
topConsumer.setControl(
- new InsetsSourceControl(ITYPE_STATUS_BAR, WindowInsets.Type.statusBars(),
+ new InsetsSourceControl(ID_STATUS_BAR, WindowInsets.Type.statusBars(),
mStatusLeash, true, new Point(0, 0), Insets.of(0, 100, 0, 0)),
new int[1], new int[1]);
- InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(ITYPE_NAVIGATION_BAR,
+ InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(ID_NAVIGATION_BAR,
WindowInsets.Type.navigationBars(), mInsetsState,
() -> mMockTransaction, mMockController);
navConsumer.setControl(
- new InsetsSourceControl(ITYPE_NAVIGATION_BAR, WindowInsets.Type.navigationBars(),
+ new InsetsSourceControl(ID_NAVIGATION_BAR, WindowInsets.Type.navigationBars(),
mNavLeash, true, new Point(400, 0), Insets.of(0, 0, 100, 0)),
new int[1], new int[1]);
mMockController.setRequestedVisibleTypes(0, WindowInsets.Type.navigationBars());
navConsumer.applyLocalVisibilityOverride();
SparseArray<InsetsSourceControl> controls = new SparseArray<>();
- controls.put(ITYPE_STATUS_BAR, topConsumer.getControl());
- controls.put(ITYPE_NAVIGATION_BAR, navConsumer.getControl());
+ controls.put(ID_STATUS_BAR, topConsumer.getControl());
+ controls.put(ID_NAVIGATION_BAR, navConsumer.getControl());
mController = new InsetsAnimationControlImpl(controls,
new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
mMockController, 10 /* durationMs */, new LinearInterpolator(),
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 5ec93e5..ca1367a 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -22,16 +22,13 @@
import static android.view.InsetsController.ANIMATION_TYPE_RESIZE;
import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
import static android.view.InsetsController.AnimationType;
+import static android.view.InsetsSource.ID_IME;
import static android.view.InsetsSourceConsumer.ShowResult.IME_SHOW_DELAYED;
import static android.view.InsetsSourceConsumer.ShowResult.SHOW_IMMEDIATELY;
-import static android.view.InsetsState.FIRST_TYPE;
-import static android.view.InsetsState.ITYPE_CAPTION_BAR;
-import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.LAST_TYPE;
import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
+import static android.view.WindowInsets.Type.SIZE;
import static android.view.WindowInsets.Type.all;
+import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.defaultVisible;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.navigationBars;
@@ -64,7 +61,6 @@
import android.graphics.Rect;
import android.os.CancellationSignal;
import android.platform.test.annotations.Presubmit;
-import android.view.InsetsState.InternalInsetsType;
import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.OnControllableInsetsChangedListener;
@@ -101,6 +97,12 @@
@Presubmit
@RunWith(AndroidJUnit4.class)
public class InsetsControllerTest {
+
+ private static final int ID_STATUS_BAR = InsetsSource.createId(
+ null /* owner */, 0 /* index */, statusBars());
+ private static final int ID_NAVIGATION_BAR = InsetsSource.createId(
+ null /* owner */, 0 /* index */, navigationBars());
+
private InsetsSource mStatusSource;
private InsetsSource mNavSource;
private InsetsSource mImeSource;
@@ -153,11 +155,11 @@
}
}, mTestHandler);
final Rect rect = new Rect(5, 5, 5, 5);
- mStatusSource = new InsetsSource(ITYPE_STATUS_BAR, statusBars());
+ mStatusSource = new InsetsSource(ID_STATUS_BAR, statusBars());
mStatusSource.setFrame(new Rect(0, 0, 100, 10));
- mNavSource = new InsetsSource(ITYPE_NAVIGATION_BAR, navigationBars());
+ mNavSource = new InsetsSource(ID_NAVIGATION_BAR, navigationBars());
mNavSource.setFrame(new Rect(0, 90, 100, 100));
- mImeSource = new InsetsSource(ITYPE_IME, ime());
+ mImeSource = new InsetsSource(ID_IME, ime());
mImeSource.setFrame(new Rect(0, 0, 100, 10));
InsetsState state = new InsetsState();
state.addSource(mStatusSource);
@@ -179,7 +181,7 @@
@Test
public void testControlsChanged() {
- mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
+ mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars()));
assertNotNull(mController.getSourceConsumer(mStatusSource).getControl().getLeash());
mController.addOnControllableInsetsChangedListener(
((controller, typeMask) -> assertEquals(statusBars(), typeMask)));
@@ -190,7 +192,7 @@
OnControllableInsetsChangedListener listener
= mock(OnControllableInsetsChangedListener.class);
mController.addOnControllableInsetsChangedListener(listener);
- mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
+ mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars()));
mController.onControlsChanged(new InsetsSourceControl[0]);
assertNull(mController.getSourceConsumer(mStatusSource).getControl());
InOrder inOrder = Mockito.inOrder(listener);
@@ -202,7 +204,7 @@
@Test
public void testControlsRevoked_duringAnim() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
+ mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars()));
ArgumentCaptor<WindowInsetsAnimationController> animationController =
ArgumentCaptor.forClass(WindowInsetsAnimationController.class);
@@ -231,7 +233,7 @@
InsetsSourceControl control =
new InsetsSourceControl(
- ITYPE_STATUS_BAR, statusBars(), mLeash, true, new Point(),
+ ID_STATUS_BAR, statusBars(), mLeash, true, new Point(),
Insets.of(0, 10, 0, 0));
mController.onControlsChanged(new InsetsSourceControl[]{control});
mController.controlWindowInsetsAnimation(0, 0 /* durationMs */,
@@ -284,7 +286,7 @@
@Test
public void testApplyImeVisibility() {
- InsetsSourceControl ime = createControl(ITYPE_IME, ime());
+ InsetsSourceControl ime = createControl(ID_IME, ime());
mController.onControlsChanged(new InsetsSourceControl[] { ime });
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.getSourceConsumer(mImeSource).onWindowFocusGained(true);
@@ -423,28 +425,28 @@
@Test
public void testRestoreStartsAnimation() {
- mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
+ mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars()));
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.hide(statusBars());
mController.cancelExistingAnimations();
assertFalse(isRequestedVisible(mController, statusBars()));
- assertFalse(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible());
+ assertFalse(mController.getState().peekSource(ID_STATUS_BAR).isVisible());
// Loosing control
InsetsState state = new InsetsState(mController.getState());
- state.setSourceVisible(ITYPE_STATUS_BAR, true);
+ state.setSourceVisible(ID_STATUS_BAR, true);
mController.onStateChanged(state);
mController.onControlsChanged(new InsetsSourceControl[0]);
assertFalse(isRequestedVisible(mController, statusBars()));
- assertTrue(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible());
+ assertTrue(mController.getState().peekSource(ID_STATUS_BAR).isVisible());
// Gaining control
- mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
+ mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars()));
assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(statusBars()));
mController.cancelExistingAnimations();
assertFalse(isRequestedVisible(mController, statusBars()));
- assertFalse(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible());
+ assertFalse(mController.getState().peekSource(ID_STATUS_BAR).isVisible());
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@@ -455,18 +457,18 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.show(ime());
- assertFalse(mController.getState().getSource(ITYPE_IME).isVisible());
+ assertFalse(mController.getState().peekSource(ID_IME).isVisible());
// Pretend IME is calling
mController.show(ime(), true /* fromIme */, null /* statsToken */);
// Gaining control shortly after
- mController.onControlsChanged(createSingletonControl(ITYPE_IME, ime()));
+ mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
mController.cancelExistingAnimations();
assertTrue(isRequestedVisible(mController, ime()));
- assertTrue(mController.getState().getSource(ITYPE_IME).isVisible());
+ assertTrue(mController.getState().peekSource(ID_IME).isVisible());
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@@ -476,10 +478,10 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.show(ime());
- assertFalse(mController.getState().getSource(ITYPE_IME).isVisible());
+ assertFalse(mController.getState().peekSource(ID_IME).isVisible());
// Gaining control shortly after
- mController.onControlsChanged(createSingletonControl(ITYPE_IME, ime()));
+ mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
// Pretend IME is calling
mController.show(ime(), true /* fromIme */, null /* statsToken */);
@@ -487,14 +489,14 @@
assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
mController.cancelExistingAnimations();
assertTrue(isRequestedVisible(mController, ime()));
- assertTrue(mController.getState().getSource(ITYPE_IME).isVisible());
+ assertTrue(mController.getState().peekSource(ID_IME).isVisible());
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@Test
public void testAnimationEndState_controller() throws Exception {
- mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
+ mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars()));
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
WindowInsetsAnimationControlListener mockListener =
@@ -520,7 +522,7 @@
@Test
public void testCancellation_afterGainingControl() throws Exception {
- mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
+ mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars()));
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
WindowInsetsAnimationControlListener mockListener =
@@ -641,61 +643,60 @@
public void testFrameUpdateDuringAnimation() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- mController.onControlsChanged(createSingletonControl(ITYPE_IME, ime()));
+ mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
// Pretend IME is calling
mController.show(ime(), true /* fromIme */, null /* statsToken */);
InsetsState copy = new InsetsState(mController.getState(), true /* copySources */);
- copy.getSource(ITYPE_IME).setFrame(0, 1, 2, 3);
- copy.getSource(ITYPE_IME).setVisibleFrame(new Rect(4, 5, 6, 7));
+ copy.peekSource(ID_IME).setFrame(0, 1, 2, 3);
+ copy.peekSource(ID_IME).setVisibleFrame(new Rect(4, 5, 6, 7));
mController.onStateChanged(copy);
assertNotEquals(new Rect(0, 1, 2, 3),
- mController.getState().getSource(ITYPE_IME).getFrame());
+ mController.getState().peekSource(ID_IME).getFrame());
assertNotEquals(new Rect(4, 5, 6, 7),
- mController.getState().getSource(ITYPE_IME).getVisibleFrame());
+ mController.getState().peekSource(ID_IME).getVisibleFrame());
mController.cancelExistingAnimations();
assertEquals(new Rect(0, 1, 2, 3),
- mController.getState().getSource(ITYPE_IME).getFrame());
+ mController.getState().peekSource(ID_IME).getFrame());
assertEquals(new Rect(4, 5, 6, 7),
- mController.getState().getSource(ITYPE_IME).getVisibleFrame());
+ mController.getState().peekSource(ID_IME).getVisibleFrame());
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@Test
public void testResizeAnimation_insetsTypes() {
- for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
- final @AnimationType int expectedAnimationType =
- (InsetsState.toPublicType(type) & systemBars()) != 0
+ for (int i = 0; i < SIZE; i++) {
+ final @InsetsType int type = 1 << i;
+ final @AnimationType int expectedAnimationType = (type & systemBars()) != 0
? ANIMATION_TYPE_RESIZE
: ANIMATION_TYPE_NONE;
doTestResizeAnimation_insetsTypes(type, expectedAnimationType);
}
}
- private void doTestResizeAnimation_insetsTypes(@InternalInsetsType int type,
+ private void doTestResizeAnimation_insetsTypes(@InsetsType int type,
@AnimationType int expectedAnimationType) {
- final @InsetsType int publicType = InsetsState.toPublicType(type);
+ final int id = type;
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
final InsetsState state1 = new InsetsState();
- state1.getSource(type).setVisible(true);
- state1.getSource(type).setFrame(0, 0, 500, 50);
+ state1.getOrCreateSource(id, type).setVisible(true).setFrame(0, 0, 500, 50);
final InsetsState state2 = new InsetsState(state1, true /* copySources */);
- state2.getSource(type).setFrame(0, 0, 500, 60);
- final String message = "Animation type of " + InsetsState.typeToString(type) + ":";
+ state2.peekSource(id).setFrame(0, 0, 500, 60);
+ final String message = "Animation type of " + WindowInsets.Type.toString(type) + ":";
// New insets source won't cause the resize animation.
mController.onStateChanged(state1);
- assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
+ assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
// Changing frame might cause the resize animation. This depends on the insets type.
mController.onStateChanged(state2);
- assertEquals(message, expectedAnimationType, mController.getAnimationType(publicType));
+ assertEquals(message, expectedAnimationType, mController.getAnimationType(type));
// Cancel the existing animations for the next iteration.
mController.cancelExistingAnimations();
- assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
+ assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@@ -703,23 +704,23 @@
@Test
public void testResizeAnimation_displayFrame() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- final @InternalInsetsType int type = ITYPE_STATUS_BAR;
- final @InsetsType int publicType = statusBars();
+ final int id = ID_STATUS_BAR;
+ final @InsetsType int type = statusBars();
final InsetsState state1 = new InsetsState();
state1.setDisplayFrame(new Rect(0, 0, 500, 1000));
- state1.getSource(type).setFrame(0, 0, 500, 50);
+ state1.getOrCreateSource(id, type).setFrame(0, 0, 500, 50);
final InsetsState state2 = new InsetsState(state1, true /* copySources */);
state2.setDisplayFrame(new Rect(0, 0, 500, 1010));
- state2.getSource(type).setFrame(0, 0, 500, 60);
+ state2.peekSource(id).setFrame(0, 0, 500, 60);
final String message = "There must not be resize animation.";
// New insets source won't cause the resize animation.
mController.onStateChanged(state1);
- assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
+ assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
// Changing frame won't cause the resize animation if the display frame is also changed.
mController.onStateChanged(state2);
- assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
+ assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@@ -727,32 +728,29 @@
@Test
public void testResizeAnimation_visibility() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- final @InternalInsetsType int type = ITYPE_STATUS_BAR;
- final @InsetsType int publicType = statusBars();
+ final int id = ID_STATUS_BAR;
+ final @InsetsType int type = statusBars();
final InsetsState state1 = new InsetsState();
- state1.getSource(type).setVisible(true);
- state1.getSource(type).setFrame(0, 0, 500, 50);
+ state1.getOrCreateSource(id, type).setVisible(true).setFrame(0, 0, 500, 50);
final InsetsState state2 = new InsetsState(state1, true /* copySources */);
- state2.getSource(type).setVisible(false);
- state2.getSource(type).setFrame(0, 0, 500, 60);
+ state2.peekSource(id).setVisible(false).setFrame(0, 0, 500, 60);
final InsetsState state3 = new InsetsState(state2, true /* copySources */);
- state3.getSource(type).setVisible(true);
- state3.getSource(type).setFrame(0, 0, 500, 70);
+ state3.peekSource(id).setVisible(true).setFrame(0, 0, 500, 70);
final String message = "There must not be resize animation.";
// New insets source won't cause the resize animation.
mController.onStateChanged(state1);
- assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
+ assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
// Changing source visibility (visible --> invisible) won't cause the resize animation.
// The previous source and the current one must be both visible.
mController.onStateChanged(state2);
- assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
+ assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
// Changing source visibility (invisible --> visible) won't cause the resize animation.
// The previous source and the current one must be both visible.
mController.onStateChanged(state3);
- assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
+ assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@@ -765,22 +763,20 @@
return;
}
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- mController.onFrameChanged(new Rect(0, 0, 100, 300));
- final InsetsState state = new InsetsState(mController.getState(), true);
- final Rect captionFrame = new Rect(0, 0, 100, 100);
- mController.setCaptionInsetsHeight(100);
- mController.onStateChanged(state);
- final InsetsState currentState = new InsetsState(mController.getState());
- // The caption bar source should be synced with the info in mAttachInfo.
- assertEquals(captionFrame, currentState.peekSource(ITYPE_CAPTION_BAR).getFrame());
- assertTrue(currentState.equals(state, true /* excludingCaptionInsets*/,
- true /* excludeInvisibleIme */));
+ final Rect frame = new Rect(0, 0, 100, 300);
+ final int captionBarHeight = 100;
+ final InsetsState state = mController.getState();
+ mController.onFrameChanged(frame);
+ mController.setCaptionInsetsHeight(captionBarHeight);
+ // The caption bar insets height should be the same as the caption bar height.
+ assertEquals(captionBarHeight, state.calculateInsets(frame, captionBar(), false).top);
// Test update to remove the caption bar
mController.setCaptionInsetsHeight(0);
- mController.onStateChanged(state);
// The caption bar source should not be there at all, because we don't add empty
// caption to the state from the server.
- assertNull(mController.getState().peekSource(ITYPE_CAPTION_BAR));
+ for (int i = state.sourceSize() - 1; i >= 0; i--) {
+ assertNotEquals(captionBar(), state.sourceAt(i).getType());
+ }
});
}
@@ -792,7 +788,6 @@
return;
}
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- final InsetsState state = new InsetsState(mController.getState(), true);
reset(mTestHost);
mController.setCaptionInsetsHeight(100);
verify(mTestHost).notifyInsetsChanged();
@@ -881,28 +876,28 @@
// Changing status bar frame should cause notifyInsetsChanged.
clearInvocations(mTestHost);
InsetsState newState = new InsetsState(localState, true /* copySources */);
- newState.getSource(ITYPE_STATUS_BAR).getFrame().bottom++;
+ newState.peekSource(ID_STATUS_BAR).getFrame().bottom++;
mController.onStateChanged(newState);
verify(mTestHost, times(1)).notifyInsetsChanged();
// Changing status bar visibility should cause notifyInsetsChanged.
clearInvocations(mTestHost);
newState = new InsetsState(localState, true /* copySources */);
- newState.getSource(ITYPE_STATUS_BAR).setVisible(false);
+ newState.peekSource(ID_STATUS_BAR).setVisible(false);
mController.onStateChanged(newState);
verify(mTestHost, times(1)).notifyInsetsChanged();
// Changing invisible IME frame should not cause notifyInsetsChanged.
clearInvocations(mTestHost);
newState = new InsetsState(localState, true /* copySources */);
- newState.getSource(ITYPE_IME).getFrame().top--;
+ newState.peekSource(ID_IME).getFrame().top--;
mController.onStateChanged(newState);
verify(mTestHost, never()).notifyInsetsChanged();
// Changing IME visibility should cause notifyInsetsChanged.
clearInvocations(mTestHost);
newState = new InsetsState(localState, true /* copySources */);
- newState.getSource(ITYPE_IME).setVisible(true);
+ newState.peekSource(ID_IME).setVisible(true);
mController.onStateChanged(newState);
verify(mTestHost, times(1)).notifyInsetsChanged();
});
@@ -947,9 +942,9 @@
}
private InsetsSourceControl[] prepareControls() {
- final InsetsSourceControl navBar = createControl(ITYPE_NAVIGATION_BAR, navigationBars());
- final InsetsSourceControl statusBar = createControl(ITYPE_STATUS_BAR, statusBars());
- final InsetsSourceControl ime = createControl(ITYPE_IME, ime());
+ final InsetsSourceControl navBar = createControl(ID_NAVIGATION_BAR, navigationBars());
+ final InsetsSourceControl statusBar = createControl(ID_STATUS_BAR, statusBars());
+ final InsetsSourceControl ime = createControl(ID_IME, ime());
InsetsSourceControl[] controls = new InsetsSourceControl[3];
controls[0] = navBar;
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 0486e3c..988e690 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -18,9 +18,8 @@
import static android.view.InsetsController.ANIMATION_TYPE_NONE;
import static android.view.InsetsController.ANIMATION_TYPE_USER;
+import static android.view.InsetsSource.ID_IME;
import static android.view.InsetsSourceConsumer.ShowResult.SHOW_IMMEDIATELY;
-import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.statusBars;
@@ -69,6 +68,9 @@
@RunWith(AndroidJUnit4.class)
public class InsetsSourceConsumerTest {
+ private static final int ID_STATUS_BAR = InsetsSource.createId(
+ null /* owner */, 0 /* index */, statusBars());
+
private InsetsSourceConsumer mConsumer;
private SurfaceSession mSession = new SurfaceSession();
@@ -97,11 +99,11 @@
// activity isn't running, lets ignore BadTokenException.
}
mState = new InsetsState();
- mSpyInsetsSource = Mockito.spy(new InsetsSource(ITYPE_STATUS_BAR, statusBars()));
+ mSpyInsetsSource = Mockito.spy(new InsetsSource(ID_STATUS_BAR, statusBars()));
mState.addSource(mSpyInsetsSource);
mController = new InsetsController(new ViewRootInsetsControllerHost(mViewRoot));
- mConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, statusBars(), mState,
+ mConsumer = new InsetsSourceConsumer(ID_STATUS_BAR, statusBars(), mState,
() -> mMockTransaction, mController) {
@Override
public void removeSurface() {
@@ -113,7 +115,7 @@
instrumentation.waitForIdleSync();
mConsumer.setControl(
- new InsetsSourceControl(ITYPE_STATUS_BAR, statusBars(), mLeash,
+ new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash,
true /* initialVisible */, new Point(), Insets.NONE),
new int[1], new int[1]);
}
@@ -147,25 +149,25 @@
InsetsController controller = new InsetsController(new ViewRootInsetsControllerHost(
mViewRoot));
InsetsSourceConsumer consumer = new InsetsSourceConsumer(
- ITYPE_IME, ime(), state, null, controller);
+ ID_IME, ime(), state, null, controller);
- InsetsSource source = new InsetsSource(ITYPE_IME, ime());
+ InsetsSource source = new InsetsSource(ID_IME, ime());
source.setFrame(0, 1, 2, 3);
consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_NONE);
// While we're animating, updates are delayed
source.setFrame(4, 5, 6, 7);
consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_USER);
- assertEquals(new Rect(0, 1, 2, 3), state.peekSource(ITYPE_IME).getFrame());
+ assertEquals(new Rect(0, 1, 2, 3), state.peekSource(ID_IME).getFrame());
// Finish the animation, now the pending frame should be applied
assertTrue(consumer.onAnimationStateChanged(false /* running */));
- assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ITYPE_IME).getFrame());
+ assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ID_IME).getFrame());
// Animating again, updates are delayed
source.setFrame(8, 9, 10, 11);
consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_USER);
- assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ITYPE_IME).getFrame());
+ assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ID_IME).getFrame());
// Updating with the current frame triggers a different code path, verify this clears
// the pending 8, 9, 10, 11 frame:
@@ -173,7 +175,7 @@
consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_USER);
assertFalse(consumer.onAnimationStateChanged(false /* running */));
- assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ITYPE_IME).getFrame());
+ assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ID_IME).getFrame());
}
@Test
@@ -185,7 +187,7 @@
verifyZeroInteractions(mMockTransaction);
int[] hideTypes = new int[1];
mConsumer.setControl(
- new InsetsSourceControl(ITYPE_STATUS_BAR, statusBars(), mLeash,
+ new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash,
true /* initialVisible */, new Point(), Insets.NONE),
new int[1], hideTypes);
assertEquals(statusBars(), hideTypes[0]);
@@ -203,7 +205,7 @@
mRemoveSurfaceCalled = false;
int[] hideTypes = new int[1];
mConsumer.setControl(
- new InsetsSourceControl(ITYPE_STATUS_BAR, statusBars(), mLeash,
+ new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash,
false /* initialVisible */, new Point(), Insets.NONE),
new int[1], hideTypes);
assertTrue(mRemoveSurfaceCalled);
@@ -219,7 +221,7 @@
ViewRootInsetsControllerHost host = new ViewRootInsetsControllerHost(mViewRoot);
InsetsController insetsController = new InsetsController(host, (controller, source) -> {
if (source.getType() == ime()) {
- return new InsetsSourceConsumer(ITYPE_IME, ime(), state,
+ return new InsetsSourceConsumer(ID_IME, ime(), state,
() -> mMockTransaction, controller) {
@Override
public int requestShow(boolean fromController,
@@ -231,11 +233,11 @@
return new InsetsSourceConsumer(source.getId(), source.getType(),
controller.getState(), Transaction::new, controller);
}, host.getHandler());
- InsetsSource imeSource = new InsetsSource(ITYPE_IME, ime());
+ InsetsSource imeSource = new InsetsSource(ID_IME, ime());
InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(imeSource);
// Initial IME insets source control with its leash.
- imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, ime(), mLeash,
+ imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash,
false /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
reset(mMockTransaction);
@@ -244,7 +246,7 @@
insetsController.controlWindowInsetsAnimation(ime(), 0L,
null /* interpolator */, null /* cancellationSignal */, null /* listener */);
assertEquals(ANIMATION_TYPE_USER, insetsController.getAnimationType(ime()));
- imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, ime(), mLeash,
+ imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash,
true /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
verify(mMockTransaction, never()).show(mLeash);
});
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index e01440c..6fa8f11 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -16,15 +16,20 @@
package android.view;
+import static android.view.WindowInsets.Type.FIRST;
+import static android.view.WindowInsets.Type.LAST;
+import static android.view.WindowInsets.Type.SIZE;
import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.navigationBars;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import android.graphics.Insets;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
import androidx.test.runner.AndroidJUnit4;
@@ -206,6 +211,21 @@
assertEquals(Insets.of(0, 0, 0, 0), insets);
}
+ @Test
+ public void testCreateId() {
+ final int numSourcePerType = 2048;
+ final int numTotalSources = SIZE * numSourcePerType;
+ final SparseArray<InsetsSource> sources = new SparseArray<>(numTotalSources);
+ final Object owner = new Object();
+ for (int index = 0; index < numSourcePerType; index++) {
+ for (int type = FIRST; type <= LAST; type = type << 1) {
+ final int id = InsetsSource.createId(owner, index, type);
+ assertNull("Must not create the same ID.", sources.get(id));
+ sources.append(id, new InsetsSource(id, type));
+ }
+ }
+ assertEquals(numTotalSources, sources.size());
+ }
// Parcel and equals already tested via InsetsStateTest
}
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 6a96f28..fde1a6d 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -18,24 +18,20 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.InsetsSource.ID_IME;
import static android.view.InsetsState.ISIDE_BOTTOM;
import static android.view.InsetsState.ISIDE_TOP;
-import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES;
-import static android.view.InsetsState.ITYPE_CAPTION_BAR;
-import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
import static android.view.RoundedCorner.POSITION_TOP_LEFT;
import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
+import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowInsets.Type.systemGestures;
import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
@@ -47,8 +43,10 @@
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import android.graphics.Insets;
@@ -76,33 +74,50 @@
@RunWith(AndroidJUnit4.class)
public class InsetsStateTest {
- private InsetsState mState = new InsetsState();
- private InsetsState mState2 = new InsetsState();
+ private static final int ID_STATUS_BAR = InsetsSource.createId(
+ null /* owner */, 0 /* index */, statusBars());
+ private static final int ID_NAVIGATION_BAR = InsetsSource.createId(
+ null /* owner */, 0 /* index */, navigationBars());
+ private static final int ID_CAPTION_BAR = InsetsSource.createId(
+ null /* owner */, 0 /* index */, captionBar());
+ private static final int ID_CLIMATE_BAR = InsetsSource.createId(
+ null /* owner */, 1 /* index */, statusBars());
+ private static final int ID_EXTRA_NAVIGATION_BAR = InsetsSource.createId(
+ null /* owner */, 1 /* index */, navigationBars());
+ private static final int ID_BOTTOM_GESTURES = InsetsSource.createId(
+ null /* owner */, 0 /* index */, systemGestures());
+
+ private final InsetsState mState = new InsetsState();
+ private final InsetsState mState2 = new InsetsState();
@Test
public void testCalculateInsets() {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
- mState.getSource(ITYPE_IME).setVisible(true);
+ mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setVisible(true);
+ mState.getOrCreateSource(ID_IME, ime())
+ .setFrame(new Rect(0, 200, 100, 300))
+ .setVisible(true);
SparseIntArray typeSideMap = new SparseIntArray();
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
false, SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
typeSideMap);
assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets());
assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all()));
- assertEquals(ISIDE_TOP, typeSideMap.get(ITYPE_STATUS_BAR));
- assertEquals(ISIDE_BOTTOM, typeSideMap.get(ITYPE_IME));
+ assertEquals(ISIDE_TOP, typeSideMap.get(ID_STATUS_BAR));
+ assertEquals(ISIDE_BOTTOM, typeSideMap.get(ID_IME));
assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars()));
assertEquals(Insets.of(0, 0, 0, 100), insets.getInsets(ime()));
}
@Test
public void testCalculateInsets_imeAndNav() {
- mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(0, 200, 100, 300));
- mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(true);
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 100, 100, 300));
- mState.getSource(ITYPE_IME).setVisible(true);
+ mState.getOrCreateSource(ID_NAVIGATION_BAR, navigationBars())
+ .setFrame(new Rect(0, 200, 100, 300))
+ .setVisible(true);
+ mState.getOrCreateSource(ID_IME, ime())
+ .setFrame(new Rect(0, 100, 100, 300))
+ .setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
false, SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
null);
@@ -116,10 +131,12 @@
@Test
public void testCalculateInsets_navRightStatusTop() {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
- mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300));
- mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(true);
+ mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setVisible(true);
+ mState.getOrCreateSource(ID_NAVIGATION_BAR, navigationBars())
+ .setFrame(new Rect(80, 0, 100, 300))
+ .setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
@@ -129,14 +146,14 @@
@Test
public void testCalculateInsets_extraNavRightClimateTop() throws Exception {
- mState.getSource(ITYPE_CLIMATE_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_CLIMATE_BAR).setVisible(true);
- mState.getSource(ITYPE_EXTRA_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300));
- mState.getSource(ITYPE_EXTRA_NAVIGATION_BAR).setVisible(true);
+ mState.getOrCreateSource(ID_CLIMATE_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setVisible(true);
+ mState.getOrCreateSource(ID_EXTRA_NAVIGATION_BAR, navigationBars())
+ .setFrame(new Rect(80, 0, 100, 300))
+ .setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
- // ITYPE_CLIMATE_BAR is a type of status bar and ITYPE_EXTRA_NAVIGATION_BAR is a type
- // of navigation bar.
assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.statusBars()));
assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.navigationBars()));
@@ -144,10 +161,12 @@
@Test
public void testCalculateInsets_imeIgnoredWithoutAdjustResize() {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
- mState.getSource(ITYPE_IME).setVisible(true);
+ mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setVisible(true);
+ mState.getOrCreateSource(ID_IME, ime())
+ .setFrame(new Rect(0, 200, 100, 300))
+ .setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
false, SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
null);
@@ -158,10 +177,12 @@
@Test
public void testCalculateInsets_systemUiFlagLayoutStable() {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(false);
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
- mState.getSource(ITYPE_IME).setVisible(true);
+ mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setVisible(false);
+ mState.getOrCreateSource(ID_IME, ime())
+ .setFrame(new Rect(0, 200, 100, 300))
+ .setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
false, SOFT_INPUT_ADJUST_NOTHING, 0, SYSTEM_UI_FLAG_LAYOUT_STABLE, TYPE_APPLICATION,
WINDOWING_MODE_UNDEFINED, null);
@@ -174,8 +195,9 @@
@Test
public void testCalculateInsets_systemUiFlagLayoutStable_windowFlagFullscreen() {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(false);
+ mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setVisible(false);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
false, SOFT_INPUT_ADJUST_NOTHING, FLAG_FULLSCREEN, SYSTEM_UI_FLAG_LAYOUT_STABLE,
TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
@@ -188,8 +210,9 @@
@Test
public void testCalculateInsets_flagLayoutNoLimits() {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+ mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
@@ -211,10 +234,12 @@
@Test
public void testCalculateInsets_captionStatusBarOverlap() {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
- mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(0, 0, 100, 300));
- mState.getSource(ITYPE_CAPTION_BAR).setVisible(true);
+ mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setVisible(true);
+ mState.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+ .setFrame(new Rect(0, 0, 100, 300))
+ .setVisible(true);
Insets visibleInsets = mState.calculateVisibleInsets(
new Rect(0, 0, 100, 400), TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
@@ -224,8 +249,9 @@
@Test
public void testCalculateInsets_captionBarOffset() {
- mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(0, 0, 100, 300));
- mState.getSource(ITYPE_CAPTION_BAR).setVisible(true);
+ mState.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+ .setFrame(new Rect(0, 0, 100, 300))
+ .setVisible(true);
Insets visibleInsets = mState.calculateVisibleInsets(
new Rect(0, 0, 150, 400), TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
@@ -235,10 +261,12 @@
@Test
public void testCalculateInsets_extraNavRightStatusTop() {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
- mState.getSource(ITYPE_EXTRA_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300));
- mState.getSource(ITYPE_EXTRA_NAVIGATION_BAR).setVisible(true);
+ mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setVisible(true);
+ mState.getOrCreateSource(ID_EXTRA_NAVIGATION_BAR, navigationBars())
+ .setFrame(new Rect(80, 0, 100, 300))
+ .setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
@@ -248,10 +276,12 @@
@Test
public void testCalculateInsets_navigationRightClimateTop() {
- mState.getSource(ITYPE_CLIMATE_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_CLIMATE_BAR).setVisible(true);
- mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300));
- mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(true);
+ mState.getOrCreateSource(ID_CLIMATE_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setVisible(true);
+ mState.getOrCreateSource(ID_NAVIGATION_BAR, navigationBars())
+ .setFrame(new Rect(80, 0, 100, 300))
+ .setVisible(true);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
@@ -261,11 +291,13 @@
@Test
public void testStripForDispatch() {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
- mState.getSource(ITYPE_IME).setVisible(true);
- mState.removeSource(ITYPE_IME);
+ mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setVisible(true);
+ mState.getOrCreateSource(ID_IME, ime())
+ .setFrame(new Rect(0, 200, 100, 300))
+ .setVisible(true);
+ mState.removeSource(ID_IME);
WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false,
SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
assertEquals(0, insets.getSystemWindowInsetBottom());
@@ -273,32 +305,42 @@
@Test
public void testEquals_differentRect() {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState2.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 10, 10));
+ mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 100, 100));
+ mState2.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 10, 10));
assertNotEqualsAndHashCode();
}
@Test
public void testEquals_differentSource() {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState2.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100));
+ mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 100, 100));
+ mState2.getOrCreateSource(ID_IME, ime())
+ .setFrame(new Rect(0, 0, 100, 100));
assertNotEqualsAndHashCode();
}
@Test
public void testEquals_sameButDifferentInsertOrder() {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100));
- mState2.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100));
- mState2.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+ mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 100, 100));
+ mState.getOrCreateSource(ID_IME, ime())
+ .setFrame(new Rect(0, 0, 100, 100));
+ mState2.getOrCreateSource(ID_IME, ime())
+ .setFrame(new Rect(0, 0, 100, 100));
+ mState2.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 100, 100));
assertEqualsAndHashCode();
}
@Test
public void testEquals_visibility() {
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_IME).setVisible(true);
- mState2.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100));
+ mState.getOrCreateSource(ID_IME, ime())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setVisible(true);
+ mState2.getOrCreateSource(ID_IME, ime())
+ .setFrame(new Rect(0, 0, 100, 100));
assertNotEqualsAndHashCode();
}
@@ -363,19 +405,23 @@
@Test
public void testEquals_excludeInvisibleIme() {
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_IME).setVisible(false);
- mState2.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 200));
- mState2.getSource(ITYPE_IME).setVisible(false);
+ mState.getOrCreateSource(ID_IME, ime())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setVisible(false);
+ mState2.getOrCreateSource(ID_IME, ime())
+ .setFrame(new Rect(0, 0, 100, 200))
+ .setVisible(false);
assertTrue(mState2.equals(mState, true, true /* excludeInvisibleIme */));
}
@Test
public void testParcelUnparcel() {
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_IME).setVisibleFrame(new Rect(0, 0, 50, 10));
- mState.getSource(ITYPE_IME).setVisible(true);
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+ mState.getOrCreateSource(ID_IME, ime())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setVisibleFrame(new Rect(0, 0, 50, 10))
+ .setVisible(true);
+ mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 100, 100));
Parcel p = Parcel.obtain();
mState.writeToParcel(p, 0 /* flags */);
p.setDataPosition(0);
@@ -386,35 +432,31 @@
@Test
public void testCopy() {
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_IME).setVisibleFrame(new Rect(0, 0, 50, 10));
- mState.getSource(ITYPE_IME).setVisible(true);
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState2.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(0, 0, 100, 100));
+ mState.getOrCreateSource(ID_IME, ime())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setVisibleFrame(new Rect(0, 0, 50, 10))
+ .setVisible(true);
+ mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 100, 100));
+ mState2.getOrCreateSource(ID_NAVIGATION_BAR, navigationBars())
+ .setFrame(new Rect(0, 0, 100, 100));
mState2.set(mState, true);
assertEquals(mState, mState2);
}
@Test
- public void testGetDefaultVisibility() {
- assertTrue(InsetsState.getDefaultVisibility(ITYPE_STATUS_BAR));
- assertTrue(InsetsState.getDefaultVisibility(ITYPE_NAVIGATION_BAR));
- assertTrue(InsetsState.getDefaultVisibility(ITYPE_CLIMATE_BAR));
- assertTrue(InsetsState.getDefaultVisibility(ITYPE_EXTRA_NAVIGATION_BAR));
- assertTrue(InsetsState.getDefaultVisibility(ITYPE_CAPTION_BAR));
- assertFalse(InsetsState.getDefaultVisibility(ITYPE_IME));
- }
-
- @Test
public void testCalculateVisibleInsets() {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
- mState.getSource(ITYPE_IME).setVisible(true);
+ mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setVisible(true);
+ mState.getOrCreateSource(ID_IME, ime())
+ .setFrame(new Rect(0, 200, 100, 300))
+ .setVisible(true);
// Make sure bottom gestures are ignored
- mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
- mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
+ mState.getOrCreateSource(ID_BOTTOM_GESTURES, systemGestures())
+ .setFrame(new Rect(0, 100, 100, 300))
+ .setVisible(true);
Insets visibleInsets = mState.calculateVisibleInsets(
new Rect(0, 0, 100, 300), TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
SOFT_INPUT_ADJUST_PAN, 0 /* windowFlags */);
@@ -423,14 +465,17 @@
@Test
public void testCalculateVisibleInsets_adjustNothing() {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
- mState.getSource(ITYPE_IME).setVisible(true);
+ mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setVisible(true);
+ mState.getOrCreateSource(ID_IME, ime())
+ .setFrame(new Rect(0, 200, 100, 300))
+ .setVisible(true);
// Make sure bottom gestures are ignored
- mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
- mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
+ mState.getOrCreateSource(ID_BOTTOM_GESTURES, systemGestures())
+ .setFrame(new Rect(0, 100, 100, 300))
+ .setVisible(true);
Insets visibleInsets = mState.calculateVisibleInsets(
new Rect(0, 0, 100, 300), TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
SOFT_INPUT_ADJUST_NOTHING, 0 /* windowFlags */);
@@ -439,14 +484,17 @@
@Test
public void testCalculateVisibleInsets_layoutNoLimits() {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
- mState.getSource(ITYPE_IME).setVisible(true);
+ mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setVisible(true);
+ mState.getOrCreateSource(ID_IME, ime())
+ .setFrame(new Rect(0, 200, 100, 300))
+ .setVisible(true);
// Make sure bottom gestures are ignored
- mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
- mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
+ mState.getOrCreateSource(ID_BOTTOM_GESTURES, systemGestures())
+ .setFrame(new Rect(0, 100, 100, 300))
+ .setVisible(true);
Insets visibleInsets = mState.calculateVisibleInsets(
new Rect(0, 0, 100, 300), TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
SOFT_INPUT_ADJUST_PAN, FLAG_LAYOUT_NO_LIMITS);
@@ -455,12 +503,15 @@
@Test
public void testCalculateUncontrollableInsets() {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 200, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 200, 300));
- mState.getSource(ITYPE_IME).setVisible(true);
- mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(100, 0, 200, 300));
- mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(true);
+ mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+ .setFrame(new Rect(0, 0, 200, 100))
+ .setVisible(true);
+ mState.getOrCreateSource(ID_IME, ime())
+ .setFrame(new Rect(0, 200, 200, 300))
+ .setVisible(true);
+ mState.getOrCreateSource(ID_NAVIGATION_BAR, navigationBars())
+ .setFrame(new Rect(100, 0, 200, 300))
+ .setVisible(true);
mState.setDisplayFrame(new Rect(0, 0, 200, 300));
assertEquals(0,
@@ -539,4 +590,77 @@
assertNotEquals(mState, mState2);
assertNotEquals(mState.hashCode(), mState2.hashCode());
}
+
+ @Test
+ public void testTraverse() {
+ // The type doesn't matter in this test.
+ final int type = statusBars();
+
+ final InsetsState insetsState1 = new InsetsState();
+ insetsState1.getOrCreateSource(2000, type);
+ insetsState1.getOrCreateSource(1000, type);
+ insetsState1.getOrCreateSource(3000, type);
+
+ final InsetsState insetsState2 = new InsetsState();
+ insetsState2.getOrCreateSource(3000, type);
+ insetsState2.getOrCreateSource(4000, type);
+ insetsState2.getOrCreateSource(2000, type);
+ insetsState2.getOrCreateSource(5000, type);
+
+ final int[] onStartCalled = {0};
+ final int[] onIdMatchCalled = {0};
+ final int[] onIdNotFoundInState1Called = {0};
+ final int[] onIdNotFoundInState2Called = {0};
+ final int[] onFinishCalled = {0};
+
+ InsetsState.traverse(insetsState1, insetsState2, new InsetsState.OnTraverseCallbacks() {
+ @Override
+ public void onStart(InsetsState state1, InsetsState state2) {
+ assertSame("state1 must be the same as insetsState1", state1, insetsState1);
+ assertSame("state2 must be the same as insetsState2", state2, insetsState2);
+ onStartCalled[0]++;
+ }
+
+ @Override
+ public void onIdMatch(InsetsSource source1, InsetsSource source2) {
+ assertNotNull("source1 must not be null.", source1);
+ assertNotNull("source2 must not be null.", source2);
+ assertEquals("Source IDs must match.", source1.getId(), source2.getId());
+ onIdMatchCalled[0]++;
+ }
+
+ @Override
+ public void onIdNotFoundInState1(int index2, InsetsSource source2) {
+ assertNotNull("source2 must not be null.", source2);
+ assertSame(source2 + " must be placed at " + index2 + " of insetsState2",
+ source2, insetsState2.sourceAt(index2));
+ assertNull("state1 must not have " + source2,
+ insetsState1.peekSource(source2.getId()));
+ onIdNotFoundInState1Called[0]++;
+ }
+
+ @Override
+ public void onIdNotFoundInState2(int index1, InsetsSource source1) {
+ assertNotNull("source1 must not be null.", source1);
+ assertSame(source1 + " must be placed at " + index1 + " of insetsState1",
+ source1, insetsState1.sourceAt(index1));
+ assertNull("state2 must not have " + source1,
+ insetsState2.peekSource(source1.getId()));
+ onIdNotFoundInState2Called[0]++;
+ }
+
+ @Override
+ public void onFinish(InsetsState state1, InsetsState state2) {
+ assertSame("state1 must be the same as insetsState1", state1, insetsState1);
+ assertSame("state2 must be the same as insetsState2", state2, insetsState2);
+ onFinishCalled[0]++;
+ }
+ });
+
+ assertEquals(1, onStartCalled[0]);
+ assertEquals(2, onIdMatchCalled[0]); // 2000 and 3000.
+ assertEquals(2, onIdNotFoundInState1Called[0]); // 4000 and 5000.
+ assertEquals(1, onIdNotFoundInState2Called[0]); // 1000.
+ assertEquals(1, onFinishCalled[0]);
+ }
}
diff --git a/core/tests/coretests/src/android/view/SurfaceControlViewHostInsetsTest.java b/core/tests/coretests/src/android/view/SurfaceControlViewHostInsetsTest.java
index 4731e81..4a00b00 100644
--- a/core/tests/coretests/src/android/view/SurfaceControlViewHostInsetsTest.java
+++ b/core/tests/coretests/src/android/view/SurfaceControlViewHostInsetsTest.java
@@ -16,35 +16,26 @@
package android.view;
+import static android.view.WindowInsets.Type.statusBars;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
-
import android.app.Instrumentation;
import android.content.Context;
import android.graphics.Insets;
-import android.graphics.Point;
import android.graphics.Rect;
import android.os.Binder;
import android.platform.test.annotations.Presubmit;
-import android.view.InsetsState.InternalInsetsType;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.WindowInsets;
-import android.view.WindowInsets.Type;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
@Presubmit
@RunWith(AndroidJUnit4.class)
public class SurfaceControlViewHostInsetsTest {
@@ -78,9 +69,12 @@
private InsetsState statusBarState(boolean visible) {
final InsetsState insetsState = new InsetsState();
+ final int id = InsetsSource.createId(null /* owner */, 0 /* index */, statusBars());
insetsState.setDisplayFrame(new Rect(0, 0, 1000, 1000));
- insetsState.getSource(ITYPE_STATUS_BAR).setVisible(visible);
- insetsState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 10));
+ insetsState.getOrCreateSource(
+ InsetsSource.createId(null /* owner */, 0 /* index */, statusBars()), statusBars())
+ .setVisible(visible)
+ .setFrame(new Rect(0, 0, 100, 10));
return insetsState;
}
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java
index e82b699..2bce747 100755
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java
@@ -45,54 +45,63 @@
.setCecSupported(isCec)
.setMhlSupported(isMhl)
.setArcSupported(isArcSupported)
- .setEarcSupported(isEarcSupported),
+ .setEarcSupported(isEarcSupported)
+ .build(),
new HdmiPortInfo.Builder(portId, portType, address)
.setCecSupported(isCec)
.setMhlSupported(isMhl)
.setArcSupported(isArcSupported)
- .setEarcSupported(isEarcSupported))
+ .setEarcSupported(isEarcSupported)
+ .build())
.addEqualityGroup(
new HdmiPortInfo.Builder(portId + 1, portType, address)
.setCecSupported(isCec)
.setMhlSupported(isMhl)
.setArcSupported(isArcSupported)
- .setEarcSupported(isEarcSupported))
+ .setEarcSupported(isEarcSupported)
+ .build())
.addEqualityGroup(
new HdmiPortInfo.Builder(portId, portType + 1, address)
.setCecSupported(isCec)
.setMhlSupported(isMhl)
.setArcSupported(isArcSupported)
- .setEarcSupported(isEarcSupported))
+ .setEarcSupported(isEarcSupported)
+ .build())
.addEqualityGroup(
new HdmiPortInfo.Builder(portId, portType, address + 1)
.setCecSupported(isCec)
.setMhlSupported(isMhl)
.setArcSupported(isArcSupported)
- .setEarcSupported(isEarcSupported))
+ .setEarcSupported(isEarcSupported)
+ .build())
.addEqualityGroup(
new HdmiPortInfo.Builder(portId, portType, address)
.setCecSupported(!isCec)
.setMhlSupported(isMhl)
.setArcSupported(isArcSupported)
- .setEarcSupported(isEarcSupported))
+ .setEarcSupported(isEarcSupported)
+ .build())
.addEqualityGroup(
new HdmiPortInfo.Builder(portId, portType, address)
.setCecSupported(isCec)
.setMhlSupported(!isMhl)
.setArcSupported(isArcSupported)
- .setEarcSupported(isEarcSupported))
+ .setEarcSupported(isEarcSupported)
+ .build())
.addEqualityGroup(
new HdmiPortInfo.Builder(portId, portType, address)
.setCecSupported(isCec)
.setMhlSupported(isMhl)
.setArcSupported(!isArcSupported)
- .setEarcSupported(isEarcSupported))
+ .setEarcSupported(isEarcSupported)
+ .build())
.addEqualityGroup(
new HdmiPortInfo.Builder(portId, portType, address)
.setCecSupported(isCec)
.setMhlSupported(isMhl)
.setArcSupported(isArcSupported)
- .setEarcSupported(!isEarcSupported))
+ .setEarcSupported(!isEarcSupported)
+ .build())
.testEquals();
}
}
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java
index 30540a5..89acbc7 100644
--- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java
@@ -131,6 +131,12 @@
assertEquals(Insets.NONE, out.getInsets());
}
+ @Test
+ public void testInvalidType() {
+ assertThrows(IllegalArgumentException.class,
+ () -> new ScreenshotRequest.Builder(5, 2).build());
+ }
+
private Bitmap makeHardwareBitmap(int width, int height) {
HardwareBuffer buffer = HardwareBuffer.create(
width, height, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index d0c3e5f..f233c6e 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -36,6 +36,12 @@
}
prebuilt_etc {
+ name: "initial-package-stopped-states.xml",
+ sub_dir: "sysconfig",
+ src: "initial-package-stopped-states.xml",
+}
+
+prebuilt_etc {
name: "preinstalled-packages-platform-overlays.xml",
product_specific: true,
sub_dir: "sysconfig",
diff --git a/data/etc/initial-package-stopped-states.xml b/data/etc/initial-package-stopped-states.xml
new file mode 100644
index 0000000..6bda2c0
--- /dev/null
+++ b/data/etc/initial-package-stopped-states.xml
@@ -0,0 +1,38 @@
+<?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.
+ -->
+
+<!--
+This XML defines an allowlist for packages that should not be scanned in a "stopped" state.
+When this feature is turned on (indicated by the config config_stopSystemPackagesByDefault in
+core/res/res/values/config.xml) packages on the system partition that are encountered by
+the PackageManagerService for the first time are scanned in the "stopped" state. This allowlist
+is also considered while creating new users on the device. Stopped state is not set during
+subsequent reboots.
+
+Example usage
+ 1. <initial-package-state package="com.example.app" stopped="false"/>
+ Indicates that a system package - com.example.app's initial stopped state should not be set
+ by the Package Manager. By default, system apps are marked as stopped.
+ 2. <initial-package-state package="com.example.app" stopped="true"/>
+ Indicates that a system package - com.example.app's initial state should be set by the
+ Package Manager to "stopped=true". It will have the same effect on the
+ package's stopped state even if this package was not included in the allow list.
+ 3. <initial-package-state package="com.example.app"/>
+ Invalid usage.
+-->
+
+<config></config>
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index 00ffd09..8cd8ddf 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -670,8 +670,12 @@
/**
* Draws a mesh object to the screen.
*
+ * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+ * ignored.</p>
+ *
* @param mesh {@link Mesh} object that will be drawn to the screen
- * @param blendMode {@link BlendMode} used to blend mesh primitives with the Paint color/shader
+ * @param blendMode {@link BlendMode} used to blend mesh primitives as the destination color
+ * with the Paint color/shader as the source color.
* @param paint {@link Paint} used to provide a color/shader/blend mode.
*/
public void drawMesh(@NonNull Mesh mesh, BlendMode blendMode, @NonNull Paint paint) {
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 2427dec..4c669b8 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -212,9 +212,9 @@
new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4);
private static final Rgb.TransferParameters BT2020_HLG_TRANSFER_PARAMETERS =
new Rgb.TransferParameters(2.0f, 2.0f, 1 / 0.17883277f,
- 0.28466892f, 0.5599107f, 0.0f, -3.0f, true);
+ 0.28466892f, 0.5599107f, -11 / 12.0f, -3.0f, true);
private static final Rgb.TransferParameters BT2020_PQ_TRANSFER_PARAMETERS =
- new Rgb.TransferParameters(107 / 128.0f, 1.0f, 32 / 2523.0f,
+ new Rgb.TransferParameters(-107 / 128.0f, 1.0f, 32 / 2523.0f,
2413 / 128.0f, -2392 / 128.0f, 8192 / 1305.0f, -2.0f, true);
// See static initialization block next to #get(Named)
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index c3eb7aa..0488b9d 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -668,7 +668,7 @@
/** @hide */
public void setTargetSdrHdrRatio(float ratio) {
- if (ratio < 1.f || Float.isNaN(ratio) || Float.isInfinite(ratio)) ratio = 1.f;
+ if (ratio < 1.f || !Float.isFinite(ratio)) ratio = 1.f;
nSetTargetSdrHdrRatio(mNativeProxy, ratio);
}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index 2830d7eff..4715045 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -801,25 +801,32 @@
));
if (mSpec.isDevicePropertiesAttestationIncluded()) {
+ final String platformReportedBrand = TextUtils.isEmpty(Build.BRAND_FOR_ATTESTATION)
+ ? Build.BRAND : Build.BRAND_FOR_ATTESTATION;
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND,
- Build.BRAND.getBytes(StandardCharsets.UTF_8)
+ platformReportedBrand.getBytes(StandardCharsets.UTF_8)
));
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE,
Build.DEVICE.getBytes(StandardCharsets.UTF_8)
));
+ final String platformReportedProduct =
+ TextUtils.isEmpty(Build.PRODUCT_FOR_ATTESTATION) ? Build.PRODUCT :
+ Build.PRODUCT_FOR_ATTESTATION;
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT,
- Build.PRODUCT.getBytes(StandardCharsets.UTF_8)
+ platformReportedProduct.getBytes(StandardCharsets.UTF_8)
));
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_MANUFACTURER,
Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8)
));
+ final String platformReportedModel = TextUtils.isEmpty(Build.MODEL_FOR_ATTESTATION)
+ ? Build.MODEL : Build.MODEL_FOR_ATTESTATION;
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_MODEL,
- Build.MODEL.getBytes(StandardCharsets.UTF_8)
+ platformReportedModel.getBytes(StandardCharsets.UTF_8)
));
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index 666b472..76e0e1e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -48,7 +48,7 @@
// TODO(b/241126279) Introduce constants to better version functionality
@Override
public int getVendorApiLevel() {
- return 2;
+ return 3;
}
@NonNull
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 3adae70..20602a1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -25,12 +25,13 @@
import android.util.ArraySet;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.window.extensions.core.util.function.Consumer;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import java.util.concurrent.Executor;
-import java.util.function.Consumer;
/**
* Reference implementation of androidx.window.extensions.area OEM interface for use with
@@ -185,6 +186,27 @@
}
}
+ @Override
+ public void addRearDisplayPresentationStatusListener(
+ @NonNull Consumer<ExtensionWindowAreaStatus> consumer) {}
+
+ @Override
+ public void removeRearDisplayPresentationStatusListener(
+ @NonNull Consumer<ExtensionWindowAreaStatus> consumer) {}
+
+ @Override
+ public void startRearDisplayPresentationSession(@NonNull Activity activity,
+ @NonNull Consumer<@WindowAreaSessionState Integer> consumer) {}
+
+ @Override
+ public void endRearDisplayPresentationSession() {}
+
+ @Override
+ @Nullable
+ public ExtensionWindowAreaPresentation getRearDisplayPresentation() {
+ return null;
+ }
+
@GuardedBy("mLock")
private int getCurrentStatus() {
if (mRearDisplaySessionStatus == WindowAreaComponent.SESSION_STATE_ACTIVE
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index 77284c41..825c670 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -17,7 +17,8 @@
package androidx.window.extensions.embedding;
import android.app.Activity;
-import android.content.res.Configuration;
+import android.os.Binder;
+import android.os.IBinder;
import android.util.Pair;
import android.util.Size;
import android.window.WindowContainerTransaction;
@@ -36,6 +37,8 @@
private final SplitRule mSplitRule;
@NonNull
private SplitAttributes mSplitAttributes;
+ @NonNull
+ private final IBinder mToken;
SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
@NonNull Activity primaryActivity,
@@ -46,6 +49,7 @@
mSecondaryContainer = secondaryContainer;
mSplitRule = splitRule;
mSplitAttributes = splitAttributes;
+ mToken = new Binder("SplitContainer");
if (shouldFinishPrimaryWithSecondary(splitRule)) {
if (mPrimaryContainer.getRunningActivityCount() == 1
@@ -83,6 +87,11 @@
return mSplitAttributes;
}
+ @NonNull
+ IBinder getToken() {
+ return mToken;
+ }
+
/**
* Updates the {@link SplitAttributes} to this container.
* It is usually used when there's a folding state change or
@@ -112,7 +121,7 @@
@NonNull
SplitInfo toSplitInfo() {
return new SplitInfo(mPrimaryContainer.toActivityStack(),
- mSecondaryContainer.toActivityStack(), mSplitAttributes);
+ mSecondaryContainer.toActivityStack(), mSplitAttributes, mToken);
}
static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 57ba6bb..ff58201 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -230,6 +230,14 @@
return mSplitAttributesCalculator;
}
+ @Override
+ @NonNull
+ public ActivityOptions setLaunchingActivityStack(@NonNull ActivityOptions options,
+ @NonNull IBinder token) {
+ options.setLaunchTaskFragmentToken(token);
+ return options;
+ }
+
@NonNull
@GuardedBy("mLock")
@VisibleForTesting
@@ -2064,6 +2072,7 @@
transactionRecord.apply(false /* shouldApplyIndependently */);
// Amend the request to let the WM know that the activity should be placed in
// the dedicated container.
+ // TODO(b/229680885): skip override launching TaskFragment token by split-rule
options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
launchedInTaskFragment.getTaskFragmentToken());
mCurrentIntent = intent;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 861cb49..38ac719 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -243,7 +243,7 @@
@NonNull
ActivityStack toActivityStack() {
- return new ActivityStack(collectNonFinishingActivities(), isEmpty());
+ return new ActivityStack(collectNonFinishingActivities(), isEmpty(), mToken);
}
/** Adds the activity that will be reparented to this container. */
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index cddbf469..7a6f46c 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 2534498..e24c228 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -126,7 +126,7 @@
private Icon mIcon;
private boolean mIsBubble;
private boolean mIsTextChanged;
- private boolean mIsClearable;
+ private boolean mIsDismissable;
private boolean mShouldSuppressNotificationDot;
private boolean mShouldSuppressNotificationList;
private boolean mShouldSuppressPeek;
@@ -181,7 +181,7 @@
@VisibleForTesting(visibility = PRIVATE)
public Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo,
final int desiredHeight, final int desiredHeightResId, @Nullable final String title,
- int taskId, @Nullable final String locus, boolean isClearable, Executor mainExecutor,
+ int taskId, @Nullable final String locus, boolean isDismissable, Executor mainExecutor,
final Bubbles.BubbleMetadataFlagListener listener) {
Objects.requireNonNull(key);
Objects.requireNonNull(shortcutInfo);
@@ -190,7 +190,7 @@
mKey = key;
mGroupKey = null;
mLocusId = locus != null ? new LocusId(locus) : null;
- mIsClearable = isClearable;
+ mIsDismissable = isDismissable;
mFlags = 0;
mUser = shortcutInfo.getUserHandle();
mPackageName = shortcutInfo.getPackage();
@@ -248,8 +248,8 @@
}
@Hide
- public boolean isClearable() {
- return mIsClearable;
+ public boolean isDismissable() {
+ return mIsDismissable;
}
/**
@@ -533,7 +533,7 @@
mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent();
}
- mIsClearable = entry.isClearable();
+ mIsDismissable = entry.isDismissable();
mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot();
mShouldSuppressNotificationList = entry.shouldSuppressNotificationList();
mShouldSuppressPeek = entry.shouldSuppressPeek();
@@ -612,7 +612,7 @@
* Whether this notification should be shown in the shade.
*/
boolean showInShade() {
- return !shouldSuppressNotification() || !mIsClearable;
+ return !shouldSuppressNotification() || !mIsDismissable;
}
/**
@@ -877,7 +877,7 @@
pw.print(" desiredHeight: "); pw.println(getDesiredHeightString());
pw.print(" suppressNotif: "); pw.println(shouldSuppressNotification());
pw.print(" autoExpand: "); pw.println(shouldAutoExpand());
- pw.print(" isClearable: "); pw.println(mIsClearable);
+ pw.print(" isDismissable: "); pw.println(mIsDismissable);
pw.println(" bubbleMetadataFlagListener null: " + (mBubbleMetadataFlagListener == null));
if (mExpandedView != null) {
mExpandedView.dump(pw);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 360bfe7..e36dfc3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -68,10 +68,14 @@
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
+import android.view.IWindowManager;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowManager;
+import android.window.ScreenCapture;
+import android.window.ScreenCapture.ScreenCaptureListener;
+import android.window.ScreenCapture.ScreenshotSync;
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
@@ -143,6 +147,7 @@
private final SyncTransactionQueue mSyncQueue;
private final ShellController mShellController;
private final ShellCommandHandler mShellCommandHandler;
+ private final IWindowManager mWmService;
// Used to post to main UI thread
private final ShellExecutor mMainExecutor;
@@ -237,7 +242,8 @@
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
TaskViewTransitions taskViewTransitions,
- SyncTransactionQueue syncQueue) {
+ SyncTransactionQueue syncQueue,
+ IWindowManager wmService) {
mContext = context;
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
@@ -269,6 +275,7 @@
mOneHandedOptional = oneHandedOptional;
mDragAndDropController = dragAndDropController;
mSyncQueue = syncQueue;
+ mWmService = wmService;
shellInit.addInitCallback(this::onInit, this);
}
@@ -1037,6 +1044,21 @@
}
/**
+ * Performs a screenshot that may exclude the bubble layer, if one is present. The screenshot
+ * can be access via the supplied {@link ScreenshotSync#get()} asynchronously.
+ *
+ * TODO(b/267324693): Implement the exclude layer functionality in screenshot.
+ */
+ public void getScreenshotExcludingBubble(int displayId,
+ Pair<ScreenCaptureListener, ScreenshotSync> screenCaptureListener) {
+ try {
+ mWmService.captureDisplay(displayId, null, screenCaptureListener.first);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to capture screenshot");
+ }
+ }
+
+ /**
* Fills the overflow bubbles by loading them from disk.
*/
void loadOverflowBubblesFromDisk() {
@@ -1750,6 +1772,25 @@
}
@Override
+ public boolean isAppBubbleTaskId(int taskId) {
+ Bubble appBubble = mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE);
+ return appBubble != null && appBubble.getTaskId() == taskId;
+ }
+
+ @Override
+ @Nullable
+ public ScreenshotSync getScreenshotExcludingBubble(int displayId) {
+ Pair<ScreenCaptureListener, ScreenshotSync> screenCaptureListener =
+ ScreenCapture.createSyncCaptureListener();
+
+ mMainExecutor.execute(
+ () -> BubbleController.this.getScreenshotExcludingBubble(displayId,
+ screenCaptureListener));
+
+ return screenCaptureListener.second;
+ }
+
+ @Override
public boolean handleDismissalInterception(BubbleEntry entry,
@Nullable List<BubbleEntry> children, IntConsumer removeCallback,
Executor callbackExecutor) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
index e3aefa5..e37c785 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
@@ -110,7 +110,7 @@
b.title,
b.taskId,
b.locusId?.id,
- b.isClearable
+ b.isDismissable
)
}
}
@@ -206,7 +206,7 @@
entity.title,
entity.taskId,
entity.locus,
- entity.isClearable,
+ entity.isDismissable,
mainExecutor,
bubbleMetadataFlagListener
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java
index 5f42826..afe19c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java
@@ -38,18 +38,18 @@
private StatusBarNotification mSbn;
private Ranking mRanking;
- private boolean mIsClearable;
+ private boolean mIsDismissable;
private boolean mShouldSuppressNotificationDot;
private boolean mShouldSuppressNotificationList;
private boolean mShouldSuppressPeek;
public BubbleEntry(@NonNull StatusBarNotification sbn,
- Ranking ranking, boolean isClearable, boolean shouldSuppressNotificationDot,
+ Ranking ranking, boolean isDismissable, boolean shouldSuppressNotificationDot,
boolean shouldSuppressNotificationList, boolean shouldSuppressPeek) {
mSbn = sbn;
mRanking = ranking;
- mIsClearable = isClearable;
+ mIsDismissable = isDismissable;
mShouldSuppressNotificationDot = shouldSuppressNotificationDot;
mShouldSuppressNotificationList = shouldSuppressNotificationList;
mShouldSuppressPeek = shouldSuppressPeek;
@@ -115,9 +115,9 @@
return mRanking.canBubble();
}
- /** @return true if this notification is clearable. */
- public boolean isClearable() {
- return mIsClearable;
+ /** @return true if this notification can be dismissed. */
+ public boolean isDismissable() {
+ return mIsDismissable;
}
/** @return true if {@link Policy#SUPPRESSED_EFFECT_BADGE} set for this notification. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index df43257..1753cda 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.bubbles;
+import static android.window.ScreenCapture.ScreenshotSync;
+
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.PARAMETER;
@@ -24,11 +26,13 @@
import android.app.NotificationChannel;
import android.content.Intent;
import android.content.pm.UserInfo;
+import android.hardware.HardwareBuffer;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.RankingMap;
import android.util.Pair;
import android.util.SparseArray;
+import android.window.ScreenCapture.ScreenshotHardwareBuffer;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
@@ -132,6 +136,18 @@
*/
void showOrHideAppBubble(Intent intent);
+ /** @return true if the specified {@code taskId} corresponds to app bubble's taskId. */
+ boolean isAppBubbleTaskId(int taskId);
+
+ /**
+ * @return a {@link ScreenshotSync} after performing a screenshot that may exclude the bubble
+ * layer, if one is present. The underlying {@link ScreenshotHardwareBuffer} can be access via
+ * {@link ScreenshotSync#get()} asynchronously and care should be taken to
+ * {@link HardwareBuffer#close()} the associated
+ * {@link ScreenshotHardwareBuffer#getHardwareBuffer()} when no longer required.
+ */
+ ScreenshotSync getScreenshotExcludingBubble(int displayId);
+
/**
* @return a bubble that matches the provided shortcutId, if one exists.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt
index f3abc27..9b2e263 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt
@@ -28,5 +28,5 @@
val title: String? = null,
val taskId: Int,
val locus: String? = null,
- val isClearable: Boolean = false
+ val isDismissable: Boolean = false
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt
index 14c053c..48d8ccf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt
@@ -43,9 +43,7 @@
private const val ATTR_TITLE = "t"
private const val ATTR_TASK_ID = "tid"
private const val ATTR_LOCUS = "l"
-
-// TODO rename it to dismissable to follow NotificationEntry namings
-private const val ATTR_CLEARABLE = "d"
+private const val ATTR_DISMISSABLE = "d"
/**
* Writes the bubbles in xml format into given output stream.
@@ -87,7 +85,7 @@
bubble.title?.let { serializer.attribute(null, ATTR_TITLE, it) }
serializer.attribute(null, ATTR_TASK_ID, bubble.taskId.toString())
bubble.locus?.let { serializer.attribute(null, ATTR_LOCUS, it) }
- serializer.attribute(null, ATTR_CLEARABLE, bubble.isClearable.toString())
+ serializer.attribute(null, ATTR_DISMISSABLE, bubble.isDismissable.toString())
serializer.endTag(null, TAG_BUBBLE)
} catch (e: IOException) {
throw RuntimeException(e)
@@ -147,7 +145,7 @@
parser.getAttributeWithName(ATTR_TITLE),
parser.getAttributeWithName(ATTR_TASK_ID)?.toInt() ?: INVALID_TASK_ID,
parser.getAttributeWithName(ATTR_LOCUS),
- parser.getAttributeWithName(ATTR_CLEARABLE)?.toBoolean() ?: false
+ parser.getAttributeWithName(ATTR_DISMISSABLE)?.toBoolean() ?: false
)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index d0aef20..a775db9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -141,7 +141,7 @@
if (pd == null) {
return false;
}
- final InsetsSource imeSource = pd.mInsetsState.getSource(InsetsState.ITYPE_IME);
+ final InsetsSource imeSource = pd.mInsetsState.peekSource(InsetsSource.ID_IME);
return imeSource != null && pd.mImeSourceControl != null && imeSource.isVisible();
}
@@ -245,14 +245,17 @@
return;
}
- updateImeVisibility(insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME));
+ updateImeVisibility(insetsState.isSourceOrDefaultVisible(InsetsSource.ID_IME,
+ WindowInsets.Type.ime()));
- final InsetsSource newSource = insetsState.getSource(InsetsState.ITYPE_IME);
- final Rect newFrame = newSource.getFrame();
- final Rect oldFrame = mInsetsState.getSource(InsetsState.ITYPE_IME).getFrame();
+ final InsetsSource newSource = insetsState.peekSource(InsetsSource.ID_IME);
+ final Rect newFrame = newSource != null ? newSource.getFrame() : null;
+ final boolean newSourceVisible = newSource != null && newSource.isVisible();
+ final InsetsSource oldSource = mInsetsState.peekSource(InsetsSource.ID_IME);
+ final Rect oldFrame = oldSource != null ? oldSource.getFrame() : null;
mInsetsState.set(insetsState, true /* copySources */);
- if (mImeShowing && !newFrame.equals(oldFrame) && newSource.isVisible()) {
+ if (mImeShowing && !Objects.equals(oldFrame, newFrame) && newSourceVisible) {
if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation");
startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */);
}
@@ -351,7 +354,7 @@
* Sends the local visibility state back to window manager. Needed for legacy adjustForIme.
*/
private void setVisibleDirectly(boolean visible) {
- mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible);
+ mInsetsState.setSourceVisible(InsetsSource.ID_IME, visible);
mRequestedVisibleTypes = visible
? mRequestedVisibleTypes | WindowInsets.Type.ime()
: mRequestedVisibleTypes & ~WindowInsets.Type.ime();
@@ -382,7 +385,7 @@
private void startAnimation(final boolean show, final boolean forceRestart,
@Nullable ImeTracker.Token statsToken) {
- final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME);
+ final InsetsSource imeSource = mInsetsState.peekSource(InsetsSource.ID_IME);
if (imeSource == null || mImeSourceControl == null) {
ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index 8484013..b8e8363 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -372,7 +372,7 @@
// Only navigation bar
if (hasNavigationBar) {
- final InsetsSource extraNavBar = insetsState.getSource(ITYPE_EXTRA_NAVIGATION_BAR);
+ final InsetsSource extraNavBar = insetsState.peekSource(ITYPE_EXTRA_NAVIGATION_BAR);
final boolean hasExtraNav = extraNavBar != null && extraNavBar.isVisible();
int position = navigationBarPosition(res, displayWidth, displayHeight, displayRotation);
int navBarSize =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index c634198..5933ac2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -37,6 +37,7 @@
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -216,13 +217,14 @@
void onInsetsChanged(InsetsState insetsState, boolean animate) {
mSplitLayout.getDividerBounds(mTempRect);
final InsetsSource taskBarInsetsSource =
- insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
// Only insets the divider bar with task bar when it's expanded so that the rounded corners
// will be drawn against task bar.
// But there is no need to do it when IME showing because there are no rounded corners at
// the bottom. This also avoids the problem of task bar height not changing when IME
// floating.
- if (!insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME)
+ if (!insetsState.isSourceOrDefaultVisible(InsetsSource.ID_IME, WindowInsets.Type.ime())
+ && taskBarInsetsSource != null
&& taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
mTempRect.inset(taskBarInsetsSource.calculateVisibleInsets(mTempRect));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 4879d86..d83f1eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -21,6 +21,7 @@
import android.os.Handler;
import android.os.UserManager;
import android.view.Choreographer;
+import android.view.IWindowManager;
import android.view.WindowManager;
import com.android.internal.jank.InteractionJankMonitor;
@@ -170,14 +171,15 @@
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
TaskViewTransitions taskViewTransitions,
- SyncTransactionQueue syncQueue) {
+ SyncTransactionQueue syncQueue,
+ IWindowManager wmService) {
return new BubbleController(context, shellInit, shellCommandHandler, shellController, data,
null /* synchronizer */, floatingContentCoordinator,
new BubbleDataRepository(context, launcherApps, mainExecutor),
statusBarService, windowManager, windowManagerShellWrapper, userManager,
launcherApps, logger, taskStackListener, organizer, positioner, displayController,
oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, bgExecutor,
- taskViewTransitions, syncQueue);
+ taskViewTransitions, syncQueue, wmService);
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
index a906522..e787ed9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
@@ -45,8 +45,9 @@
@VisibleForTesting
final SizeSpecSource mSizeSpecSourceImpl;
- /** The preferred minimum (and default) size specified by apps. */
+ /** The preferred minimum (and default minimum) size specified by apps. */
@Nullable private Size mOverrideMinSize;
+ private int mOverridableMinSize;
/** Used to store values obtained from resource files. */
private Point mScreenEdgeInsets;
@@ -386,6 +387,8 @@
mDefaultMinSize = res.getDimensionPixelSize(
R.dimen.default_minimal_size_pip_resizable_task);
+ mOverridableMinSize = res.getDimensionPixelSize(
+ R.dimen.overridable_minimal_size_pip_resizable_task);
final String screenEdgeInsetsDpString = res.getString(
R.string.config_defaultPictureInPictureScreenEdgeInsets);
@@ -443,13 +446,19 @@
/** Returns the preferred minimal size specified by the activity in PIP. */
@Nullable
public Size getOverrideMinSize() {
+ if (mOverrideMinSize != null
+ && (mOverrideMinSize.getWidth() < mOverridableMinSize
+ || mOverrideMinSize.getHeight() < mOverridableMinSize)) {
+ return new Size(mOverridableMinSize, mOverridableMinSize);
+ }
+
return mOverrideMinSize;
}
/** Returns the minimum edge size of the override minimum size, or 0 if not set. */
public int getOverrideMinEdgeSize() {
if (mOverrideMinSize == null) return 0;
- return Math.min(mOverrideMinSize.getWidth(), mOverrideMinSize.getHeight());
+ return Math.min(getOverrideMinSize().getWidth(), getOverrideMinSize().getHeight());
}
public int getMinEdgeSize() {
@@ -505,7 +514,7 @@
if (mOverrideMinSize == null) {
return null;
}
- final Size size = mOverrideMinSize;
+ final Size size = getOverrideMinSize();
final float sizeAspectRatio = size.getWidth() / (float) size.getHeight();
if (sizeAspectRatio > aspectRatio) {
// Size is wider, fix the width and increase the height
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
index eab82f0..87e447b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
@@ -85,7 +85,7 @@
@Override
public void insetsChanged(InsetsState insetsState) {
- mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ mTaskbarInsetsSource = insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
AnimationContext context = mAnimationContextByTaskId.valueAt(i);
context.update(mTaskbarInsetsSource, context.mTaskInfo);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
index 6e10ebe..8a0fbe3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
@@ -126,7 +126,7 @@
@Override
public void insetsChanged(InsetsState insetsState) {
- mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ mTaskbarInsetsSource = insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
updateContexts();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 476a7ec..2981f5e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -36,6 +36,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.transition.Transitions;
/**
* View model for the window decoration with a caption and shadows. Works with
@@ -65,6 +66,9 @@
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
mSyncQueue = syncQueue;
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mTaskOperations = new TaskOperations(null, mContext, mSyncQueue);
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index 907977c..3734487 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -29,7 +29,8 @@
*/
public interface WindowDecorViewModel {
/**
- * Sets the transition starter that starts freeform task transitions.
+ * Sets the transition starter that starts freeform task transitions. Only called when
+ * {@link com.android.wm.shell.transition.Transitions#ENABLE_SHELL_TRANSITIONS} is {@code true}.
*
* @param transitionStarter the transition starter that starts freeform task transitions
*/
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
similarity index 96%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
index b3a2ad3..7358da3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
@@ -45,7 +45,7 @@
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class MultiBubblesScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+open class ChangeActiveActivityFromBubbleTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
@Before
open fun before() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt
similarity index 88%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt
copy to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt
index 69eb06f..1a0fbe4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt
@@ -23,4 +23,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class MultiBubblesScreenCfArm(flicker: FlickerTest) : MultiBubblesScreen(flicker)
+open class ChangeActiveActivityFromBubbleTestCfArm(flicker: FlickerTest) :
+ ChangeActiveActivityFromBubbleTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestShellTransit.kt
similarity index 91%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestShellTransit.kt
index 191f4fa..cf696c8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestShellTransit.kt
@@ -30,7 +30,8 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FlakyTest(bugId = 217777115)
-class MultiBubblesScreenShellTransit(flicker: FlickerTest) : MultiBubblesScreen(flicker) {
+class ChangeActiveActivityFromBubbleTestShellTransit(flicker: FlickerTest) :
+ ChangeActiveActivityFromBubbleTest(flicker) {
@Before
override fun before() {
Assume.assumeTrue(isShellTransitionsEnabled)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenCfArm.kt
deleted file mode 100644
index 315e021..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenCfArm.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.bubble
-
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class DismissBubbleScreenCfArm(flicker: FlickerTest) : DismissBubbleScreen(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
similarity index 96%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
index 7a86c25..9367a8a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
@@ -44,7 +44,7 @@
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class DismissBubbleScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+open class DragToDismissBubbleScreenTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
private val displaySize = DisplayMetrics()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt
similarity index 89%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt
copy to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt
index 69eb06f..85a534c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt
@@ -23,4 +23,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class MultiBubblesScreenCfArm(flicker: FlickerTest) : MultiBubblesScreen(flicker)
+class DragToDismissBubbleScreenTestCfArm(flicker: FlickerTest) :
+ DragToDismissBubbleScreenTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenCfArm.kt
deleted file mode 100644
index 26e6273..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenCfArm.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.bubble
-
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class ExpandBubbleScreenCfArm(flicker: FlickerTest) : ExpandBubbleScreen(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenCfArm.kt
deleted file mode 100644
index ec6a9c1..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenCfArm.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.bubble
-
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class LaunchBubbleScreenCfArm(flicker: FlickerTest) : LaunchBubbleScreen(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLockreenTest.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLockreenTest.kt
index 379d5e9..0b1382b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLockreenTest.kt
@@ -46,7 +46,7 @@
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class LaunchBubbleFromLockScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+class OpenActivityFromBubbleOnLockreenTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
similarity index 95%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
index 0cda626..50507bf 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
@@ -42,7 +42,7 @@
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class ExpandBubbleScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+open class OpenActivityFromBubbleTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt
similarity index 90%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt
index 69eb06f..94147e8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt
@@ -23,4 +23,4 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class MultiBubblesScreenCfArm(flicker: FlickerTest) : MultiBubblesScreen(flicker)
+class OpenActivityFromBubbleTestCfArm(flicker: FlickerTest) : OpenActivityFromBubbleTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
similarity index 95%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
index 5c0f854..4be4dcd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
@@ -41,7 +41,7 @@
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class LaunchBubbleScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+open class SendBubbleNotificationTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt
similarity index 89%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt
copy to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt
index 69eb06f..7efbcdb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt
@@ -23,4 +23,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class MultiBubblesScreenCfArm(flicker: FlickerTest) : MultiBubblesScreen(flicker)
+open class SendBubbleNotificationTestCfArm(flicker: FlickerTest) :
+ SendBubbleNotificationTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index 7aa40e7..82617dd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -82,13 +82,33 @@
@Presubmit
@Test
- override fun pipAppLayerAlwaysVisible() {
- if (!flicker.scenario.isGesturalNavigation) super.pipAppLayerAlwaysVisible()
- else {
- // pip layer in gesture nav will disappear during transition
- flicker.assertLayers {
- this.isVisible(pipApp).then().isInvisible(pipApp).then().isVisible(pipApp)
- }
+ override fun pipAppLayerOrOverlayAlwaysVisible() {
+ // pip layer in gesture nav will disappear during transition
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.pipAppLayerOrOverlayAlwaysVisible()
+ }
+
+ @Presubmit
+ @Test
+ fun pipAppWindowVisibleChanges() {
+ // pip layer in gesture nav will disappear during transition
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ flicker.assertWm {
+ this.isAppWindowVisible(pipApp)
+ .then()
+ .isAppWindowInvisible(pipApp, isOptional = true)
+ .then()
+ .isAppWindowVisible(pipApp, isOptional = true)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun pipAppLayerVisibleChanges() {
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ // pip layer in gesture nav will disappear during transition
+ flicker.assertLayers {
+ this.isVisible(pipApp).then().isInvisible(pipApp).then().isVisible(pipApp)
}
}
@@ -116,12 +136,19 @@
@Presubmit
@Test
- override fun pipLayerRemainInsideVisibleBounds() {
- if (!flicker.scenario.isGesturalNavigation) super.pipLayerRemainInsideVisibleBounds()
- else {
- // pip layer in gesture nav will disappear during transition
- flicker.assertLayersStart { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
- flicker.assertLayersEnd { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
- }
+ override fun pipLayerOrOverlayRemainInsideVisibleBounds() {
+ // pip layer in gesture nav will disappear during transition
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.pipLayerOrOverlayRemainInsideVisibleBounds()
+ }
+
+ @Presubmit
+ @Test
+ fun pipLayerRemainInsideVisibleBounds() {
+ // pip layer in gesture nav will disappear during transition
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ // pip layer in gesture nav will disappear during transition
+ flicker.assertLayersStart { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
+ flicker.assertLayersEnd { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 5d7003f..33a6405 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -74,8 +74,14 @@
/** Checks [pipApp] layer remains visible throughout the animation */
@Presubmit
@Test
- open fun pipAppLayerAlwaysVisible() {
- flicker.assertLayers { this.isVisible(pipApp) }
+ open fun pipAppLayerOrOverlayAlwaysVisible() {
+ flicker.assertLayers {
+ this.isVisible(pipApp)
+ .then()
+ .isVisible(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
+ .then()
+ .isVisible(pipApp)
+ }
}
/**
@@ -94,8 +100,10 @@
*/
@Presubmit
@Test
- open fun pipLayerRemainInsideVisibleBounds() {
- flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ open fun pipLayerOrOverlayRemainInsideVisibleBounds() {
+ flicker.assertLayersVisibleRegion(pipApp.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY) ) {
+ coversAtMost(displayBounds)
+ }
}
/** Checks that the visible region of [pipApp] always reduces during the animation */
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index 02d50f4..afc4106 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.flicker.pip
import android.app.Activity
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
@@ -197,7 +198,7 @@
}
/** {@inheritDoc} */
- @Presubmit
+ @FlakyTest(bugId = 267424412)
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
super.visibleLayersShownMoreThanOneConsecutiveEntry()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
index 7466916..691b087 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
@@ -19,6 +19,7 @@
import android.platform.test.annotations.Presubmit
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.Test
/** Base class for pip expand tests */
@@ -32,7 +33,9 @@
@Presubmit
@Test
open fun pipAppWindowRemainInsideVisibleBounds() {
- flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ flicker.assertWmVisibleRegion(pipApp.or(ComponentNameMatcher.TRANSITION_SNAPSHOT)) {
+ coversAtMost(displayBounds)
+ }
}
/**
@@ -42,7 +45,9 @@
@Presubmit
@Test
open fun pipAppLayerRemainInsideVisibleBounds() {
- flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ flicker.assertLayersVisibleRegion(pipApp.or(ComponentNameMatcher.TRANSITION_SNAPSHOT)) {
+ coversAtMost(displayBounds)
+ }
}
/**
@@ -72,7 +77,10 @@
@Test
open fun showBothAppLayersThenHidePip() {
flicker.assertLayers {
- isVisible(testApp).isVisible(pipApp).then().isInvisible(testApp).isVisible(pipApp)
+ isVisible(testApp)
+ .isVisible(pipApp.or(ComponentNameMatcher.TRANSITION_SNAPSHOT))
+ .then()
+ .isInvisible(testApp).isVisible(pipApp)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index 54ad991..1b48965 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -76,7 +76,9 @@
?.height
?: error("Couldn't find Nav or Task bar layer")
// The dismiss button doesn't appear at the complete bottom of the screen,
- val displayY = device.displayHeight - barLayerHeight
+ // it appears above the hot seat but `hotseatBarSize` is not available outside
+ // the platform
+ val displayY = (device.displayHeight * 0.9).toInt() - barLayerHeight
device.swipe(pipCenterX, pipCenterY, displayCenterX, displayY, 50)
// Wait until the other app is no longer visible
wmHelper
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt
index c02e2d1..3bfbcd2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt
@@ -35,7 +35,7 @@
private val user0Bubbles = listOf(
BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1,
- isClearable = true),
+ isDismissable = true),
BubbleEntity(10, "com.example.chat", "alice and bob", "0k2", 0, 16537428, "title", 2,
null),
BubbleEntity(0, "com.example.messenger", "shortcut-2", "0k3", 120, 0, null,
@@ -44,7 +44,7 @@
private val user1Bubbles = listOf(
BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3,
- isClearable = true),
+ isDismissable = true),
BubbleEntity(12, "com.example.chat", "alice and bob", "1k2", 0, 16537428, "title", 4,
null),
BubbleEntity(1, "com.example.messenger", "shortcut-2", "1k3", 120, 0, null,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index ffb1a4d..01e2f98 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -17,7 +17,7 @@
package com.android.wm.shell.common;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
import static android.view.Surface.ROTATION_0;
import static android.view.WindowInsets.Type.ime;
@@ -138,15 +138,15 @@
private InsetsSourceControl[] insetsSourceControl() {
return new InsetsSourceControl[]{
new InsetsSourceControl(
- ITYPE_IME, ime(), mock(SurfaceControl.class), false, new Point(0, 0),
+ ID_IME, ime(), mock(SurfaceControl.class), false, new Point(0, 0),
Insets.NONE)
};
}
private InsetsState insetsStateWithIme(boolean visible) {
InsetsState state = new InsetsState();
- state.addSource(new InsetsSource(ITYPE_IME, ime()));
- state.setSourceVisible(ITYPE_IME, visible);
+ state.addSource(new InsetsSource(ID_IME, ime()));
+ state.setSourceVisible(ID_IME, visible);
return state;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index 3d8cd2e..ec264a6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -300,9 +300,9 @@
(MAX_ASPECT_RATIO + DEFAULT_ASPECT_RATIO) / 2
};
final Size[] minimalSizes = new Size[] {
- new Size((int) (100 * aspectRatios[0]), 100),
- new Size((int) (100 * aspectRatios[1]), 100),
- new Size((int) (100 * aspectRatios[2]), 100)
+ new Size((int) (200 * aspectRatios[0]), 200),
+ new Size((int) (200 * aspectRatios[1]), 200),
+ new Size((int) (200 * aspectRatios[2]), 200)
};
for (int i = 0; i < aspectRatios.length; i++) {
final float aspectRatio = aspectRatios[i];
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index ede5bde..341a451 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -162,10 +162,10 @@
@Test
public void testSetOverrideMinSize_notChanged_callbackNotInvoked() {
final Runnable callback = mock(Runnable.class);
- mPipBoundsState.setOverrideMinSize(new Size(5, 5));
+ mPipBoundsState.setOverrideMinSize(new Size(100, 150));
mPipBoundsState.setOnMinimalSizeChangeCallback(callback);
- mPipBoundsState.setOverrideMinSize(new Size(5, 5));
+ mPipBoundsState.setOverrideMinSize(new Size(100, 150));
verify(callback, never()).run();
}
@@ -175,11 +175,11 @@
mPipBoundsState.setOverrideMinSize(null);
assertEquals(0, mPipBoundsState.getOverrideMinEdgeSize());
- mPipBoundsState.setOverrideMinSize(new Size(5, 10));
- assertEquals(5, mPipBoundsState.getOverrideMinEdgeSize());
+ mPipBoundsState.setOverrideMinSize(new Size(100, 110));
+ assertEquals(100, mPipBoundsState.getOverrideMinEdgeSize());
- mPipBoundsState.setOverrideMinSize(new Size(15, 10));
- assertEquals(10, mPipBoundsState.getOverrideMinEdgeSize());
+ mPipBoundsState.setOverrideMinSize(new Size(150, 200));
+ assertEquals(150, mPipBoundsState.getOverrideMinEdgeSize());
}
@Test
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index dd6c2bc..eeed226 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -553,6 +553,7 @@
"RootRenderNode.cpp",
"SkiaCanvas.cpp",
"SkiaInterpolator.cpp",
+ "Tonemapper.cpp",
"VectorDrawable.cpp",
],
@@ -611,7 +612,6 @@
"ProfileData.cpp",
"ProfileDataContainer.cpp",
"Readback.cpp",
- "Tonemapper.cpp",
"TreeInfo.cpp",
"WebViewFunctorManager.cpp",
"protos/graphicsstats.proto",
diff --git a/libs/hwui/CanvasTransform.h b/libs/hwui/CanvasTransform.h
index c46a2d3..291f4cf 100644
--- a/libs/hwui/CanvasTransform.h
+++ b/libs/hwui/CanvasTransform.h
@@ -45,4 +45,4 @@
SkColor transformColor(ColorTransform transform, SkColor color);
SkColor transformColorInverse(ColorTransform transform, SkColor color);
-} // namespace android::uirenderer;
\ No newline at end of file
+} // namespace android::uirenderer
diff --git a/libs/hwui/ColorMode.h b/libs/hwui/ColorMode.h
index e45db01..959cf74 100644
--- a/libs/hwui/ColorMode.h
+++ b/libs/hwui/ColorMode.h
@@ -27,6 +27,9 @@
WideColorGamut = 1,
// Extended range Display P3
Hdr = 2,
+ // Extended range Display P3 10-bit
+ // for test purposes only, not shippable due to insuffient alpha
+ Hdr10 = 3,
// Alpha 8
A8 = 4,
};
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 3f7c4f0..430e69e 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -43,6 +43,7 @@
#include "SkRegion.h"
#include "SkTextBlob.h"
#include "SkVertices.h"
+#include "Tonemapper.h"
#include "VectorDrawable.h"
#include "include/gpu/GpuTypes.h" // from Skia
#include "include/gpu/GrDirectContext.h"
@@ -344,7 +345,9 @@
SkPaint paint;
BitmapPalette palette;
void draw(SkCanvas* c, const SkMatrix&) const {
- c->drawImage(image.get(), x, y, sampling, &paint);
+ SkPaint newPaint = paint;
+ tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint);
+ c->drawImage(image.get(), x, y, sampling, &newPaint);
}
};
struct DrawImageRect final : Op {
@@ -366,7 +369,9 @@
SkCanvas::SrcRectConstraint constraint;
BitmapPalette palette;
void draw(SkCanvas* c, const SkMatrix&) const {
- c->drawImageRect(image.get(), src, dst, sampling, &paint, constraint);
+ SkPaint newPaint = paint;
+ tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint);
+ c->drawImageRect(image.get(), src, dst, sampling, &newPaint, constraint);
}
};
struct DrawImageLattice final : Op {
@@ -399,8 +404,10 @@
auto flags =
(0 == fs) ? nullptr : pod<SkCanvas::Lattice::RectType>(
this, (xs + ys) * sizeof(int) + fs * sizeof(SkColor));
- c->drawImageLattice(image.get(), {xdivs, ydivs, flags, xs, ys, &src, colors}, dst,
- filter, &paint);
+ SkPaint newPaint = paint;
+ tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint);
+ c->drawImageLattice(image.get(), {xdivs, ydivs, flags, xs, ys, &src, colors}, dst, filter,
+ &newPaint);
}
};
diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp
index a7e76b6..0d39f0e 100644
--- a/libs/hwui/Tonemapper.cpp
+++ b/libs/hwui/Tonemapper.cpp
@@ -18,7 +18,10 @@
#include <SkRuntimeEffect.h>
#include <log/log.h>
+// libshaders only exists on Android devices
+#ifdef __ANDROID__
#include <shaders/shaders.h>
+#endif
#include "utils/Color.h"
@@ -26,6 +29,8 @@
namespace {
+// custom tonemapping only exists on Android devices
+#ifdef __ANDROID__
class ColorFilterRuntimeEffectBuilder : public SkRuntimeEffectBuilder {
public:
explicit ColorFilterRuntimeEffectBuilder(sk_sp<SkRuntimeEffect> effect)
@@ -59,20 +64,21 @@
return effectBuilder.makeColorFilter();
}
-static bool extractTransfer(ui::Dataspace dataspace) {
- return dataspace & HAL_DATASPACE_TRANSFER_MASK;
+static ui::Dataspace extractTransfer(ui::Dataspace dataspace) {
+ return static_cast<ui::Dataspace>(dataspace & HAL_DATASPACE_TRANSFER_MASK);
}
static bool isHdrDataspace(ui::Dataspace dataspace) {
const auto transfer = extractTransfer(dataspace);
- return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG;
+ return transfer == ui::Dataspace::TRANSFER_ST2084 || transfer == ui::Dataspace::TRANSFER_HLG;
}
static ui::Dataspace getDataspace(const SkImageInfo& image) {
return static_cast<ui::Dataspace>(
ColorSpaceToADataSpace(image.colorSpace(), image.colorType()));
}
+#endif
} // namespace
@@ -80,6 +86,8 @@
// shader and tag it on the supplied paint.
void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, float maxLuminanceNits,
SkPaint& paint) {
+// custom tonemapping only exists on Android devices
+#ifdef __ANDROID__
const auto sourceDataspace = getDataspace(source);
const auto destinationDataspace = getDataspace(destination);
@@ -102,6 +110,9 @@
paint.setColorFilter(colorFilter);
}
}
+#else
+ return;
+#endif
}
} // namespace android::uirenderer
diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
index 4886fdd..3e453e6 100644
--- a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
@@ -79,7 +79,7 @@
return (jlong)proxy;
}
-static void HardwareBufferRenderer_destroy(jobject renderProxy) {
+static void HardwareBufferRenderer_destroy(jlong renderProxy) {
auto* proxy = reinterpret_cast<RenderProxy*>(renderProxy);
delete proxy;
}
@@ -109,7 +109,7 @@
return matrix;
}
-static int android_graphics_HardwareBufferRenderer_render(JNIEnv* env, jobject, jobject renderProxy,
+static int android_graphics_HardwareBufferRenderer_render(JNIEnv* env, jobject, jlong renderProxy,
jint transform, jint width, jint height,
jlong colorspacePtr, jobject consumer) {
auto* proxy = reinterpret_cast<RenderProxy*>(renderProxy);
@@ -119,11 +119,17 @@
auto colorSpace = GraphicsJNI::getNativeColorSpace(colorspacePtr);
proxy->setHardwareBufferRenderParams(
HardwareBufferRenderParams(matrix, colorSpace, createRenderCallback(env, consumer)));
+ nsecs_t vsync = systemTime(SYSTEM_TIME_MONOTONIC);
+ UiFrameInfoBuilder(proxy->frameInfo())
+ .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID,
+ UiFrameInfoBuilder::UNKNOWN_DEADLINE,
+ UiFrameInfoBuilder::UNKNOWN_FRAME_INTERVAL)
+ .addFlag(FrameInfoFlags::SurfaceCanvas);
return proxy->syncAndDrawFrame();
}
static void android_graphics_HardwareBufferRenderer_setLightGeometry(JNIEnv*, jobject,
- jobject renderProxyPtr,
+ jlong renderProxyPtr,
jfloat lightX, jfloat lightY,
jfloat lightZ,
jfloat lightRadius) {
@@ -132,7 +138,7 @@
}
static void android_graphics_HardwareBufferRenderer_setLightAlpha(JNIEnv* env, jobject,
- jobject renderProxyPtr,
+ jlong renderProxyPtr,
jfloat ambientShadowAlpha,
jfloat spotShadowAlpha) {
auto* proxy = reinterpret_cast<RenderProxy*>(renderProxyPtr);
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 2017eb6..6628463 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -646,6 +646,11 @@
mSurfaceColorSpace = SkColorSpace::MakeRGB(
GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
break;
+ case ColorMode::Hdr10:
+ mSurfaceColorType = SkColorType::kRGBA_1010102_SkColorType;
+ mSurfaceColorSpace = SkColorSpace::MakeRGB(
+ GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
+ break;
case ColorMode::A8:
mSurfaceColorType = SkColorType::kAlpha_8_SkColorType;
mSurfaceColorSpace = nullptr;
@@ -654,7 +659,7 @@
}
void SkiaPipeline::setTargetSdrHdrRatio(float ratio) {
- if (mColorMode == ColorMode::Hdr) {
+ if (mColorMode == ColorMode::Hdr || mColorMode == ColorMode::Hdr10) {
mTargetSdrHdrRatio = ratio;
mSurfaceColorSpace = SkColorSpace::MakeRGB(GetExtendedTransferFunction(mTargetSdrHdrRatio),
SkNamedGamut::kDisplayP3);
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index ee1c2ec..f10b2b2 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -194,8 +194,6 @@
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) {
@@ -303,7 +301,8 @@
float CanvasContext::setColorMode(ColorMode mode) {
if (mode != mColorMode) {
- if (mode == ColorMode::Hdr && !mRenderPipeline->supportsExtendedRangeHdr()) {
+ const bool isHdr = mode == ColorMode::Hdr || mode == ColorMode::Hdr10;
+ if (isHdr && !mRenderPipeline->supportsExtendedRangeHdr()) {
mode = ColorMode::WideColorGamut;
}
mColorMode = mode;
@@ -313,13 +312,15 @@
switch (mColorMode) {
case ColorMode::Hdr:
return 3.f; // TODO: Refine this number
+ case ColorMode::Hdr10:
+ return 10.f;
default:
return 1.f;
}
}
float CanvasContext::targetSdrHdrRatio() const {
- if (mColorMode == ColorMode::Hdr) {
+ if (mColorMode == ColorMode::Hdr || mColorMode == ColorMode::Hdr10) {
return mTargetSdrHdrRatio;
} else {
return 1.f;
@@ -1064,6 +1065,10 @@
mSyncDelayDuration = duration;
}
+void CanvasContext::startHintSession() {
+ mHintSessionWrapper.init();
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index a811670..b26c018 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -229,6 +229,8 @@
void setSyncDelayDuration(nsecs_t duration);
+ void startHintSession();
+
private:
CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline,
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 5b7cf75..4fb114b 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -455,6 +455,7 @@
// composer3 support, just treat HDR as equivalent to wide color gamut if
// the GLES path is still being hit
case ColorMode::Hdr:
+ case ColorMode::Hdr10:
case ColorMode::WideColorGamut: {
skcms_Matrix3x3 colorGamut;
LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&colorGamut),
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 1e011c2..31b4b20 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -45,8 +45,12 @@
pid_t uiThreadId = pthread_gettid_np(pthread_self());
pid_t renderThreadId = getRenderThreadTid();
mContext = mRenderThread.queue().runSync([=, this]() -> CanvasContext* {
- return CanvasContext::create(mRenderThread, translucent, rootRenderNode, contextFactory,
- uiThreadId, renderThreadId);
+ CanvasContext* context = CanvasContext::create(mRenderThread, translucent, rootRenderNode,
+ contextFactory, uiThreadId, renderThreadId);
+ if (context != nullptr) {
+ mRenderThread.queue().post([=] { context->startHintSession(); });
+ }
+ return context;
});
mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode);
}
@@ -146,7 +150,7 @@
float RenderProxy::setColorMode(ColorMode mode) {
// We only need to figure out what the renderer supports for HDR, otherwise this can stay
// an async call since we already know the return value
- if (mode == ColorMode::Hdr) {
+ if (mode == ColorMode::Hdr || mode == ColorMode::Hdr10) {
return mRenderThread.queue().runSync(
[=]() -> float { return mContext->setColorMode(mode); });
} else {
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index 2efa5d6..21b6c44 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -201,7 +201,7 @@
outWindowInfo->colorspace = colorSpace;
outWindowInfo->colorMode = colorMode;
- if (colorMode == ColorMode::Hdr) {
+ if (colorMode == ColorMode::Hdr || colorMode == ColorMode::Hdr10) {
outWindowInfo->dataspace =
static_cast<android_dataspace>(STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_EXTENDED);
} else {
@@ -509,7 +509,7 @@
mNativeBuffers[i].skSurface.reset();
}
- if (mWindowInfo.colorMode == ColorMode::Hdr) {
+ if (mWindowInfo.colorMode == ColorMode::Hdr || mWindowInfo.colorMode == ColorMode::Hdr10) {
mWindowInfo.dataspace =
static_cast<android_dataspace>(STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_EXTENDED);
} else {
@@ -521,7 +521,7 @@
"Unsupported colorspace");
if (mNativeWindow) {
- int err = native_window_set_buffers_data_space(mNativeWindow.get(), mWindowInfo.dataspace);
+ int err = ANativeWindow_setBuffersDataSpace(mNativeWindow.get(), mWindowInfo.dataspace);
if (err != 0) {
ALOGE("VulkanSurface::setColorSpace() native_window_set_buffers_data_space(%d) "
"failed: %s (%d)",
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 1022d93..28496f1 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -1086,8 +1086,16 @@
}
/**
- * Sets the visibility of this route to public. This is the default
- * visibility for routes that are public to all other apps.
+ * Sets the visibility of this route to public.
+ *
+ * <p>By default, unless you call {@link #setVisibilityRestricted}, the new route will be
+ * public.
+ *
+ * <p>Public routes are visible to any application with a matching {@link
+ * RouteDiscoveryPreference#getPreferredFeatures feature}.
+ *
+ * <p>Calls to this method override previous calls to {@link #setVisibilityPublic} and
+ * {@link #setVisibilityRestricted}.
*/
@NonNull
public Builder setVisibilityPublic() {
@@ -1097,8 +1105,16 @@
}
/**
- * Sets the visibility of this route to restricted. This means that the
- * route is only visible to a set of package name.
+ * Sets the visibility of this route to restricted.
+ *
+ * <p>Routes with restricted visibility are only visible to its publisher application and
+ * applications whose package name is included in the provided {@code allowedPackages} set
+ * with a matching {@link RouteDiscoveryPreference#getPreferredFeatures feature}.
+ *
+ * <p>Calls to this method override previous calls to {@link #setVisibilityPublic} and
+ * {@link #setVisibilityRestricted}.
+ *
+ * @see #setVisibilityPublic
* @param allowedPackages set of package names which are allowed to see this route.
*/
@NonNull
diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java
index ff0a492..a26249a 100644
--- a/media/java/android/media/RouteListingPreference.java
+++ b/media/java/android/media/RouteListingPreference.java
@@ -17,7 +17,6 @@
package android.media;
import android.annotation.IntDef;
-import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
@@ -45,14 +44,24 @@
public final class RouteListingPreference implements Parcelable {
/**
- * {@link Intent} action for apps to take the user to a screen for transferring media playback
- * to the route with the id provided by the extra with key {@link #EXTRA_ROUTE_ID}.
+ * {@link Intent} action that the system uses to take the user the app when the user selects an
+ * {@link Item} whose {@link Item#getSelectionBehavior() selection behavior} is {@link
+ * Item#SELECTION_BEHAVIOR_GO_TO_APP}.
+ *
+ * <p>The launched intent will identify the selected item using the extra identified by {@link
+ * #EXTRA_ROUTE_ID}.
+ *
+ * @see #getLinkedItemComponentName()
+ * @see Item#SELECTION_BEHAVIOR_GO_TO_APP
*/
public static final String ACTION_TRANSFER_MEDIA = "android.media.action.TRANSFER_MEDIA";
/**
* {@link Intent} string extra key that contains the {@link Item#getRouteId() id} of the route
* to transfer to, as part of an {@link #ACTION_TRANSFER_MEDIA} intent.
+ *
+ * @see #getLinkedItemComponentName()
+ * @see Item#SELECTION_BEHAVIOR_GO_TO_APP
*/
public static final String EXTRA_ROUTE_ID = "android.media.extra.ROUTE_ID";
@@ -72,12 +81,12 @@
@NonNull private final List<Item> mItems;
private final boolean mUseSystemOrdering;
- @Nullable private final ComponentName mInAppOnlyItemRoutingReceiver;
+ @Nullable private final ComponentName mLinkedItemComponentName;
private RouteListingPreference(Builder builder) {
mItems = builder.mItems;
mUseSystemOrdering = builder.mUseSystemOrdering;
- mInAppOnlyItemRoutingReceiver = builder.mInAppOnlyItemRoutingReceiver;
+ mLinkedItemComponentName = builder.mLinkedItemComponentName;
}
private RouteListingPreference(Parcel in) {
@@ -85,7 +94,7 @@
in.readParcelableList(new ArrayList<>(), Item.class.getClassLoader(), Item.class);
mItems = List.copyOf(items);
mUseSystemOrdering = in.readBoolean();
- mInAppOnlyItemRoutingReceiver = ComponentName.readFromParcel(in);
+ mLinkedItemComponentName = ComponentName.readFromParcel(in);
}
/**
@@ -110,18 +119,20 @@
}
/**
- * Returns a {@link ComponentName} for handling routes disabled via {@link
- * Item#DISABLE_REASON_IN_APP_ONLY}, or null if the user needs to manually navigate to the app
- * in order to route to select the corresponding routes.
+ * Returns a {@link ComponentName} for navigating to the application.
*
- * <p>If the user selects an {@link Item} disabled via {@link Item#DISABLE_REASON_IN_APP_ONLY},
- * and this method returns a non-null {@link ComponentName}, the system takes the user back to
- * the app by launching an intent to the returned {@link ComponentName}, using action {@link
- * #ACTION_TRANSFER_MEDIA}, with the extra {@link #EXTRA_ROUTE_ID}.
+ * <p>Must not be null if any of the {@link #getItems() items} of this route listing preference
+ * has {@link Item#getSelectionBehavior() selection behavior} {@link
+ * Item#SELECTION_BEHAVIOR_GO_TO_APP}.
+ *
+ * <p>The system navigates to the application when the user selects {@link Item} with {@link
+ * Item#SELECTION_BEHAVIOR_GO_TO_APP} by launching an intent to the returned {@link
+ * ComponentName}, using action {@link #ACTION_TRANSFER_MEDIA}, with the extra {@link
+ * #EXTRA_ROUTE_ID}.
*/
@Nullable
- public ComponentName getInAppOnlyItemRoutingReceiver() {
- return mInAppOnlyItemRoutingReceiver;
+ public ComponentName getLinkedItemComponentName() {
+ return mLinkedItemComponentName;
}
// RouteListingPreference Parcelable implementation.
@@ -135,7 +146,7 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeParcelableList(mItems, flags);
dest.writeBoolean(mUseSystemOrdering);
- ComponentName.writeToParcel(mInAppOnlyItemRoutingReceiver, dest);
+ ComponentName.writeToParcel(mLinkedItemComponentName, dest);
}
// Equals and hashCode.
@@ -151,13 +162,12 @@
RouteListingPreference that = (RouteListingPreference) other;
return mItems.equals(that.mItems)
&& mUseSystemOrdering == that.mUseSystemOrdering
- && Objects.equals(
- mInAppOnlyItemRoutingReceiver, that.mInAppOnlyItemRoutingReceiver);
+ && Objects.equals(mLinkedItemComponentName, that.mLinkedItemComponentName);
}
@Override
public int hashCode() {
- return Objects.hash(mItems, mUseSystemOrdering, mInAppOnlyItemRoutingReceiver);
+ return Objects.hash(mItems, mUseSystemOrdering, mLinkedItemComponentName);
}
/** Builder for {@link RouteListingPreference}. */
@@ -165,7 +175,7 @@
private List<Item> mItems;
private boolean mUseSystemOrdering;
- private ComponentName mInAppOnlyItemRoutingReceiver;
+ private ComponentName mLinkedItemComponentName;
/** Creates a new instance with default values (documented in the setters). */
public Builder() {
@@ -198,14 +208,13 @@
}
/**
- * See {@link #getInAppOnlyItemRoutingReceiver()}.
+ * See {@link #getLinkedItemComponentName()}.
*
* <p>The default value is {@code null}.
*/
@NonNull
- public Builder setInAppOnlyItemRoutingReceiver(
- @Nullable ComponentName inAppOnlyItemRoutingReceiver) {
- mInAppOnlyItemRoutingReceiver = inAppOnlyItemRoutingReceiver;
+ public Builder setLinkedItemComponentName(@Nullable ComponentName linkedItemComponentName) {
+ mLinkedItemComponentName = linkedItemComponentName;
return this;
}
@@ -225,6 +234,29 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(
+ prefix = {"SELECTION_BEHAVIOR_"},
+ value = {
+ SELECTION_BEHAVIOR_NONE,
+ SELECTION_BEHAVIOR_TRANSFER,
+ SELECTION_BEHAVIOR_GO_TO_APP
+ })
+ public @interface SelectionBehavior {}
+
+ /** The corresponding route is not selectable by the user. */
+ public static final int SELECTION_BEHAVIOR_NONE = 0;
+ /** If the user selects the corresponding route, the media transfers to the said route. */
+ public static final int SELECTION_BEHAVIOR_TRANSFER = 1;
+ /**
+ * If the user selects the corresponding route, the system takes the user to the
+ * application.
+ *
+ * <p>The system uses {@link #getLinkedItemComponentName()} in order to navigate to the app.
+ */
+ public static final int SELECTION_BEHAVIOR_GO_TO_APP = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
flag = true,
prefix = {"FLAG_"},
value = {FLAG_ONGOING_SESSION, FLAG_SUGGESTED_ROUTE})
@@ -249,48 +281,42 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(
- prefix = {"DISABLE_REASON_"},
+ prefix = {"SUBTEXT_"},
value = {
- DISABLE_REASON_NONE,
- DISABLE_REASON_SUBSCRIPTION_REQUIRED,
- DISABLE_REASON_DOWNLOADED_CONTENT,
- DISABLE_REASON_AD,
- DISABLE_REASON_IN_APP_ONLY,
- DISABLE_REASON_CUSTOM
+ SUBTEXT_NONE,
+ SUBTEXT_SUBSCRIPTION_REQUIRED,
+ SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED,
+ SUBTEXT_AD_ROUTING_DISALLOWED,
+ SUBTEXT_CUSTOM
})
- public @interface DisableReason {}
+ public @interface SubText {}
- /** The corresponding route is available for routing. */
- public static final int DISABLE_REASON_NONE = 0;
+ /** The corresponding route has no associated subtext. */
+ public static final int SUBTEXT_NONE = 0;
/**
- * The corresponding route requires a special subscription in order to be available for
- * routing.
+ * The corresponding route's subtext must indicate that it requires a special subscription
+ * in order to be available for routing.
*/
- public static final int DISABLE_REASON_SUBSCRIPTION_REQUIRED = 1;
+ public static final int SUBTEXT_SUBSCRIPTION_REQUIRED = 1;
/**
- * The corresponding route is not available because downloaded content cannot be routed to
- * it.
+ * The corresponding route's subtext must indicate that downloaded content cannot be routed
+ * to it.
*/
- public static final int DISABLE_REASON_DOWNLOADED_CONTENT = 2;
- /** The corresponding route is not available because an ad is in progress. */
- public static final int DISABLE_REASON_AD = 3;
+ public static final int SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED = 2;
/**
- * The corresponding route is only available for routing from within the app.
- *
- * <p>The user may still select the corresponding route if the app provides an {@link
- * #getInAppOnlyItemRoutingReceiver() in-app routing receiver}, in which case the system
- * will take the user to the app.
+ * The corresponding route's subtext must indicate that it is not available because an ad is
+ * in progress.
*/
- public static final int DISABLE_REASON_IN_APP_ONLY = 4;
+ public static final int SUBTEXT_AD_ROUTING_DISALLOWED = 3;
/**
- * The corresponding route is not available because of the reason described by {@link
- * #getCustomDisableReasonMessage()}.
+ * The corresponding route's subtext must be obtained from {@link
+ * #getCustomSubtextMessage()}.
*
* <p>Applications should strongly prefer one of the other disable reasons (for the full
- * list, see {@link #getDisableReason()}) in order to guarantee correct localization and
- * rendering across all form factors.
+ * list, see {@link #getSubText()}) in order to guarantee correct localization and rendering
+ * across all form factors.
*/
- public static final int DISABLE_REASON_CUSTOM = 5;
+ public static final int SUBTEXT_CUSTOM = 10000;
@NonNull
public static final Creator<Item> CREATOR =
@@ -307,29 +333,27 @@
};
@NonNull private final String mRouteId;
+ @SelectionBehavior private final int mSelectionBehavior;
@Flags private final int mFlags;
- @DisableReason private final int mDisableReason;
- private final int mSessionParticipantCount;
- @Nullable private final CharSequence mCustomDisableReasonMessage;
+ @SubText private final int mSubText;
+
+ @Nullable private final CharSequence mCustomSubtextMessage;
private Item(@NonNull Builder builder) {
mRouteId = builder.mRouteId;
+ mSelectionBehavior = builder.mSelectionBehavior;
mFlags = builder.mFlags;
- mDisableReason = builder.mDisableReason;
- mSessionParticipantCount = builder.mSessionParticipantCount;
- mCustomDisableReasonMessage = builder.mCustomDisableReasonMessage;
- validateCustomDisableReasonMessage();
+ mSubText = builder.mSubText;
+ mCustomSubtextMessage = builder.mCustomSubtextMessage;
}
private Item(Parcel in) {
mRouteId = in.readString();
Preconditions.checkArgument(!TextUtils.isEmpty(mRouteId));
+ mSelectionBehavior = in.readInt();
mFlags = in.readInt();
- mDisableReason = in.readInt();
- mSessionParticipantCount = in.readInt();
- Preconditions.checkArgument(mSessionParticipantCount >= 0);
- mCustomDisableReasonMessage = in.readCharSequence();
- validateCustomDisableReasonMessage();
+ mSubText = in.readInt();
+ mCustomSubtextMessage = in.readCharSequence();
}
/**
@@ -343,6 +367,17 @@
}
/**
+ * Returns the behavior that the corresponding route has if the user selects it.
+ *
+ * @see #SELECTION_BEHAVIOR_NONE
+ * @see #SELECTION_BEHAVIOR_TRANSFER
+ * @see #SELECTION_BEHAVIOR_GO_TO_APP
+ */
+ public int getSelectionBehavior() {
+ return mSelectionBehavior;
+ }
+
+ /**
* Returns the flags associated to the route that corresponds to this item.
*
* @see #FLAG_ONGOING_SESSION
@@ -354,49 +389,42 @@
}
/**
- * Returns the reason for the corresponding route to be disabled, or {@link
- * #DISABLE_REASON_NONE} if the route is not disabled.
+ * Returns the type of subtext associated to this route.
*
- * @see #DISABLE_REASON_NONE
- * @see #DISABLE_REASON_SUBSCRIPTION_REQUIRED
- * @see #DISABLE_REASON_DOWNLOADED_CONTENT
- * @see #DISABLE_REASON_AD
- * @see #DISABLE_REASON_IN_APP_ONLY
- * @see #DISABLE_REASON_CUSTOM
+ * <p>Subtext types other than {@link #SUBTEXT_NONE} and {@link #SUBTEXT_CUSTOM} must not
+ * have {@link #SELECTION_BEHAVIOR_TRANSFER}.
+ *
+ * <p>If this method returns {@link #SUBTEXT_CUSTOM}, then the subtext is obtained form
+ * {@link #getCustomSubtextMessage()}.
+ *
+ * @see #SUBTEXT_NONE
+ * @see #SUBTEXT_SUBSCRIPTION_REQUIRED
+ * @see #SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED
+ * @see #SUBTEXT_AD_ROUTING_DISALLOWED
+ * @see #SUBTEXT_CUSTOM
*/
- @DisableReason
- public int getDisableReason() {
- return mDisableReason;
+ @SubText
+ public int getSubText() {
+ return mSubText;
}
/**
- * Returns a non-negative number of participants in the ongoing session (if any) on the
- * corresponding route.
+ * Returns a human-readable {@link CharSequence} providing the subtext for the corresponding
+ * route.
*
- * <p>The system ignores this value if zero, or if {@link #getFlags()} does not include
- * {@link #FLAG_ONGOING_SESSION}.
- */
- public int getSessionParticipantCount() {
- return mSessionParticipantCount;
- }
-
- /**
- * Returns a human-readable {@link CharSequence} describing the reason for this route to be
- * disabled. May be null if {@link #getDisableReason()} is not {@link
- * #DISABLE_REASON_CUSTOM}.
- *
- * <p>This value is ignored if the {@link #getDisableReason() disable reason} for this item
- * is not {@link #DISABLE_REASON_CUSTOM}.
+ * <p>This value is ignored if the {@link #getSubText() subtext} for this item is not {@link
+ * #SUBTEXT_CUSTOM}..
*
* <p>Applications must provide a localized message that matches the system's locale. See
* {@link Locale#getDefault()}.
*
- * <p>This message is a hint for the system. Applications should strongly prefer one of the
- * other disable reasons listed in {@link #getDisableReason()}.
+ * <p>Applications should avoid using custom messages (and instead use one of non-custom
+ * subtexts listed in {@link #getSubText()} in order to guarantee correct visual
+ * representation and localization on all form factors.
*/
@Nullable
- public CharSequence getCustomDisableReasonMessage() {
- return mCustomDisableReasonMessage;
+ public CharSequence getCustomSubtextMessage() {
+ return mCustomSubtextMessage;
}
// Item Parcelable implementation.
@@ -409,10 +437,10 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mRouteId);
+ dest.writeInt(mSelectionBehavior);
dest.writeInt(mFlags);
- dest.writeInt(mDisableReason);
- dest.writeInt(mSessionParticipantCount);
- dest.writeCharSequence(mCustomDisableReasonMessage);
+ dest.writeInt(mSubText);
+ dest.writeCharSequence(mCustomSubtextMessage);
}
// Equals and hashCode.
@@ -427,40 +455,26 @@
}
Item item = (Item) other;
return mRouteId.equals(item.mRouteId)
+ && mSelectionBehavior == item.mSelectionBehavior
&& mFlags == item.mFlags
- && mDisableReason == item.mDisableReason
- && mSessionParticipantCount == item.mSessionParticipantCount
- && TextUtils.equals(
- mCustomDisableReasonMessage, item.mCustomDisableReasonMessage);
+ && mSubText == item.mSubText
+ && TextUtils.equals(mCustomSubtextMessage, item.mCustomSubtextMessage);
}
@Override
public int hashCode() {
return Objects.hash(
- mRouteId,
- mFlags,
- mDisableReason,
- mSessionParticipantCount,
- mCustomDisableReasonMessage);
- }
-
- private void validateCustomDisableReasonMessage() {
- if (mDisableReason == DISABLE_REASON_CUSTOM) {
- Preconditions.checkArgument(
- !TextUtils.isEmpty(mCustomDisableReasonMessage),
- "customDisableReasonMessage must not be null or empty if disable reason is"
- + " DISABLE_REASON_CUSTOM.");
- }
+ mRouteId, mSelectionBehavior, mFlags, mSubText, mCustomSubtextMessage);
}
/** Builder for {@link Item}. */
public static final class Builder {
private final String mRouteId;
+ private int mSelectionBehavior;
private int mFlags;
- private int mDisableReason;
- private int mSessionParticipantCount;
- private CharSequence mCustomDisableReasonMessage;
+ private int mSubText;
+ private CharSequence mCustomSubtextMessage;
/**
* Constructor.
@@ -470,39 +484,51 @@
public Builder(@NonNull String routeId) {
Preconditions.checkArgument(!TextUtils.isEmpty(routeId));
mRouteId = routeId;
- mDisableReason = DISABLE_REASON_NONE;
+ mSelectionBehavior = SELECTION_BEHAVIOR_TRANSFER;
+ mSubText = SUBTEXT_NONE;
}
- /** See {@link Item#getFlags()}. */
+ /**
+ * See {@link Item#getSelectionBehavior()}.
+ *
+ * <p>The default value is {@link #ACTION_TRANSFER_MEDIA}.
+ */
+ @NonNull
+ public Builder setSelectionBehavior(int selectionBehavior) {
+ mSelectionBehavior = selectionBehavior;
+ return this;
+ }
+
+ /**
+ * See {@link Item#getFlags()}.
+ *
+ * <p>The default value is zero (no flags).
+ */
@NonNull
public Builder setFlags(int flags) {
mFlags = flags;
return this;
}
- /** See {@link Item#getDisableReason()}. */
+ /**
+ * See {@link Item#getSubText()}.
+ *
+ * <p>The default value is {@link #SUBTEXT_NONE}.
+ */
@NonNull
- public Builder setDisableReason(int disableReason) {
- mDisableReason = disableReason;
+ public Builder setSubText(int subText) {
+ mSubText = subText;
return this;
}
- /** See {@link Item#getSessionParticipantCount()}. */
+ /**
+ * See {@link Item#getCustomSubtextMessage()}.
+ *
+ * <p>The default value is {@code null}.
+ */
@NonNull
- public Builder setSessionParticipantCount(
- @IntRange(from = 0) int sessionParticipantCount) {
- Preconditions.checkArgument(
- sessionParticipantCount >= 0,
- "sessionParticipantCount must be non-negative.");
- mSessionParticipantCount = sessionParticipantCount;
- return this;
- }
-
- /** See {@link Item#getCustomDisableReasonMessage()}. */
- @NonNull
- public Builder setCustomDisableReasonMessage(
- @Nullable CharSequence customDisableReasonMessage) {
- mCustomDisableReasonMessage = customDisableReasonMessage;
+ public Builder setCustomSubtextMessage(@Nullable CharSequence customSubtextMessage) {
+ mCustomSubtextMessage = customSubtextMessage;
return this;
}
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 985ac3c..9e9012e 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -253,23 +253,19 @@
public void onStop() { }
/**
- * Indicates the width and height of the captured region in pixels. Called immediately after
- * capture begins to provide the app with accurate sizing for the stream. Also called
- * when the region captured in this MediaProjection session is resized.
+ * Invoked immediately after capture begins or when the size of the captured region changes,
+ * providing the accurate sizing for the streamed capture.
* <p>
* The given width and height, in pixels, corresponds to the same width and height that
- * would be returned from {@link android.view.WindowMetrics#getBounds()}
+ * would be returned from {@link android.view.WindowMetrics#getBounds()} of the captured
+ * region.
* </p>
* <p>
- * Without the application resizing the {@link VirtualDisplay} (returned from
- * {@code MediaProjection#createVirtualDisplay}) and output {@link Surface} (provided
- * to {@code MediaProjection#createVirtualDisplay}), the captured stream will have
- * letterboxing (black bars) around the recorded content to make up for the
- * difference in aspect ratio.
- * </p>
- * <p>
- * The application can prevent the letterboxing by overriding this method, and
- * updating the size of both the {@link VirtualDisplay} and output {@link Surface}:
+ * If the recorded content has a different aspect ratio from either the
+ * {@link VirtualDisplay} or output {@link Surface}, the captured stream has letterboxing
+ * (black bars) around the recorded content. The application can avoid the letterboxing
+ * around the recorded content by updating the size of both the {@link VirtualDisplay} and
+ * output {@link Surface}:
* </p>
*
* <pre>
@@ -293,27 +289,29 @@
public void onCapturedContentResize(int width, int height) { }
/**
- * Indicates the visibility of the captured region has changed. Called immediately after
- * capture begins with the initial visibility state, and when visibility changes. Provides
- * the app with accurate state for presenting its own UI. The application can take advantage
- * of this by showing or hiding the captured content, based on if the captured region is
- * currently visible to the user.
+ * Invoked immediately after capture begins or when the visibility of the captured region
+ * changes, providing the current visibility of the captured region.
+ * <p>
+ * Applications can take advantage of this callback by showing or hiding the captured
+ * content from the output {@link Surface}, based on if the captured region is currently
+ * visible to the user.
+ * </p>
* <p>
* For example, if the user elected to capture a single app (from the activity shown from
- * {@link MediaProjectionManager#createScreenCaptureIntent()}), the callback may be
- * triggered for the following reasons:
+ * {@link MediaProjectionManager#createScreenCaptureIntent()}), the following scenarios
+ * trigger the callback:
* <ul>
* <li>
- * The captured region may become visible ({@code isVisible} with value
- * {@code true}), because the captured app is at least partially visible. This may
- * happen if the captured app was previously covered by another app. The other app
- * moves to show at least some portion of the captured app.
+ * The captured region is visible ({@code isVisible} with value {@code true}),
+ * because the captured app is at least partially visible. This may happen if the
+ * user moves the covering app to show at least some portion of the captured app
+ * (e.g. the user has multiple apps visible in a multi-window mode such as split
+ * screen).
* </li>
* <li>
- * The captured region may become invisible ({@code isVisible} with value
- * {@code false}) if it is entirely hidden. This may happen if the captured app is
- * entirely covered by another app, or the user navigates away from the captured
- * app.
+ * The captured region is invisible ({@code isVisible} with value {@code false}) if
+ * it is entirely hidden. This may happen if another app entirely covers the
+ * captured app, or the user navigates away from the captured app.
* </li>
* </ul>
* </p>
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 59e19ed..1a3e54d 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -25,6 +25,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.media.AudioFormat;
@@ -50,6 +51,7 @@
* VoiceInteractionService} instead. Access to this class is protected by a permission
* granted only to system or privileged apps.
* @deprecated use {@link SoundTriggerManager} directly
+ *
* @hide
*/
@Deprecated
@@ -67,7 +69,7 @@
private final Object mLock = new Object();
private final ISoundTriggerSession mSoundTriggerSession;
- private final UUID mSoundModelId;
+ private final GenericSoundModel mSoundModel;
private final Callback mCallback;
private final Handler mHandler;
private final RecognitionCallback mRecognitionCallback;
@@ -277,10 +279,11 @@
* This class should be constructed by the {@link SoundTriggerManager}.
* @hide
*/
- SoundTriggerDetector(ISoundTriggerSession soundTriggerSession, UUID soundModelId,
+ SoundTriggerDetector(ISoundTriggerSession soundTriggerSession,
+ @NonNull GenericSoundModel soundModel,
@NonNull Callback callback, @Nullable Handler handler) {
mSoundTriggerSession = soundTriggerSession;
- mSoundModelId = soundModelId;
+ mSoundModel = soundModel;
mCallback = callback;
if (handler == null) {
mHandler = new MyHandler();
@@ -320,7 +323,7 @@
int status;
try {
- status = mSoundTriggerSession.startRecognition(new ParcelUuid(mSoundModelId),
+ status = mSoundTriggerSession.startRecognition(mSoundModel,
mRecognitionCallback, new RecognitionConfig(captureTriggerAudio,
allowMultipleTriggers, null, null, audioCapabilities),
runInBatterySaver);
@@ -339,7 +342,7 @@
public boolean stopRecognition() {
int status = STATUS_OK;
try {
- status = mSoundTriggerSession.stopRecognition(new ParcelUuid(mSoundModelId),
+ status = mSoundTriggerSession.stopRecognition(new ParcelUuid(mSoundModel.getUuid()),
mRecognitionCallback);
} catch (RemoteException e) {
return false;
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index 8dc708a..c473a01 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -184,11 +184,16 @@
if (oldInstance != null) {
// Shutdown old instance.
}
- SoundTriggerDetector newInstance = new SoundTriggerDetector(mSoundTriggerSession,
- soundModelId, callback, handler);
- mReceiverInstanceMap.put(soundModelId, newInstance);
- return newInstance;
- }
+ try {
+ SoundTriggerDetector newInstance = new SoundTriggerDetector(mSoundTriggerSession,
+ mSoundTriggerSession.getSoundModel(new ParcelUuid(soundModelId)),
+ callback, handler);
+ mReceiverInstanceMap.put(soundModelId, newInstance);
+ return newInstance;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
/**
* Class captures the data and fields that represent a non-keyphrase sound model. Use the
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 360073d..cdaa3e5 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -606,22 +606,22 @@
}
/**
- * Receives requested recording info.
+ * Receives the requested {@link android.media.tv.TvRecordingInfo}.
*
- * @param recordingInfo The requested recording info. Null if recording not found.
- * @hide
+ * @see #requestTvRecordingInfo(String)
+ * @param recordingInfo The requested recording info. {@code null} if no recording found.
*/
public void onTvRecordingInfo(@Nullable TvRecordingInfo recordingInfo) {
}
/**
- * Receives requested recording info.
+ * Receives requested recording info list.
*
- * @param recordingInfoList The requested recording info list. Null if recording not found.
- * @hide
+ * @see #requestTvRecordingInfoList(int)
+ * @param recordingInfoList The list of recording info requested. Returns an empty list if
+ * no matching recording info found.
*/
- public void onTvRecordingInfoList(@Nullable List<TvRecordingInfo> recordingInfoList) {
- }
+ public void onTvRecordingInfoList(@NonNull List<TvRecordingInfo> recordingInfoList) {}
/**
* Receives started recording's ID.
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 78c6bcf..0b44a89 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -635,8 +635,8 @@
/**
* Sends the requested {@link android.media.tv.TvRecordingInfo}.
*
- * @param recordingInfo The recording info requested {@code null} if no recording found.
- * @hide
+ * @see TvInteractiveAppService.Session#requestTvRecordingInfo(String)
+ * @param recordingInfo The recording info requested. {@code null} if no recording found.
*/
public void sendTvRecordingInfo(@Nullable TvRecordingInfo recordingInfo) {
if (DEBUG) {
@@ -650,10 +650,11 @@
/**
* Sends the requested {@link android.media.tv.TvRecordingInfo}.
*
- * @param recordingInfoList The list of recording info requested.
- * @hide
+ * @see TvInteractiveAppService.Session#requestTvRecordingInfoList(int)
+ * @param recordingInfoList The list of recording info requested. Returns an empty list if no
+ * matching recording info found.
*/
- public void sendTvRecordingInfoList(@Nullable List<TvRecordingInfo> recordingInfoList) {
+ public void sendTvRecordingInfoList(@NonNull List<TvRecordingInfo> recordingInfoList) {
if (DEBUG) {
Log.d(TAG, "sendTvRecordingInfoList");
}
diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java
index d958db1..8f2e7b5 100644
--- a/media/java/android/media/tv/tuner/filter/MediaEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java
@@ -18,10 +18,15 @@
import android.annotation.BytesLong;
import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.media.AudioPresentation;
import android.media.MediaCodec.LinearBlock;
+import java.util.Collections;
+import java.util.List;
+
/**
* Filter event sent from {@link Filter} objects with media type.
*
@@ -51,12 +56,13 @@
private final boolean mIsPrivateData;
private final int mScIndexMask;
private final AudioDescriptor mExtraMetaData;
+ private final List<AudioPresentation> mAudioPresentations;
// This constructor is used by JNI code only
private MediaEvent(int streamId, boolean isPtsPresent, long pts, boolean isDtsPresent, long dts,
long dataLength, long offset, LinearBlock buffer, boolean isSecureMemory, long dataId,
int mpuSequenceNumber, boolean isPrivateData, int scIndexMask,
- AudioDescriptor extraMetaData) {
+ AudioDescriptor extraMetaData, List<AudioPresentation> audioPresentations) {
mStreamId = streamId;
mIsPtsPresent = isPtsPresent;
mPts = pts;
@@ -71,6 +77,7 @@
mIsPrivateData = isPrivateData;
mScIndexMask = scIndexMask;
mExtraMetaData = extraMetaData;
+ mAudioPresentations = audioPresentations;
}
/**
@@ -215,6 +222,13 @@
return mExtraMetaData;
}
+ /**
+ * Gets audio presentations.
+ */
+ @NonNull
+ public List<AudioPresentation> getAudioPresentations() {
+ return mAudioPresentations == null ? Collections.emptyList() : mAudioPresentations;
+ }
/**
* Finalize the MediaEvent object.
diff --git a/media/java/android/media/tv/tuner/frontend/IptvFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IptvFrontendSettings.java
index fb11ea9..ba75102 100644
--- a/media/java/android/media/tv/tuner/frontend/IptvFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/IptvFrontendSettings.java
@@ -92,7 +92,7 @@
private final long mBitrate;
private final String mContentUrl;
- public IptvFrontendSettings(@NonNull byte[] srcIpAddress, @NonNull byte[] dstIpAddress,
+ private 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);
diff --git a/media/java/android/media/tv/tuner/frontend/IptvFrontendSettingsFec.java b/media/java/android/media/tv/tuner/frontend/IptvFrontendSettingsFec.java
index 699d615..a70af17 100644
--- a/media/java/android/media/tv/tuner/frontend/IptvFrontendSettingsFec.java
+++ b/media/java/android/media/tv/tuner/frontend/IptvFrontendSettingsFec.java
@@ -62,7 +62,7 @@
private final int mFecRowNum;
private final int mFecColNum;
- public IptvFrontendSettingsFec(@FecType int fecType, int fecRowNum, int fecColNum) {
+ private IptvFrontendSettingsFec(@FecType int fecType, int fecRowNum, int fecColNum) {
mFecType = fecType;
mFecRowNum = fecRowNum;
mFecColNum = fecColNum;
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index a0304bb..ed1072c 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -88,6 +88,7 @@
"android.hidl.memory@1.0",
"android.hidl.token@1.0-utils",
"android.hardware.drm-V1-ndk",
+ "android.hardware.tv.tuner-V2-ndk",
],
header_libs: [
diff --git a/media/jni/android_media_AudioPresentation.h b/media/jni/android_media_AudioPresentation.h
index a3adddd..0c83ddf 100644
--- a/media/jni/android_media_AudioPresentation.h
+++ b/media/jni/android_media_AudioPresentation.h
@@ -19,10 +19,14 @@
#include "jni.h"
+#include <aidl/android/hardware/tv/tuner/AudioPresentation.h>
#include <media/stagefright/foundation/ADebug.h> // CHECK
#include <media/stagefright/foundation/AudioPresentationInfo.h>
#include <nativehelper/ScopedLocalRef.h>
+using ::aidl::android::hardware::tv::tuner::AudioPreselectionRenderingIndicationType;
+using TunerAudioPresentation = ::aidl::android::hardware::tv::tuner::AudioPresentation;
+
namespace android {
struct JAudioPresentationInfo {
@@ -97,6 +101,38 @@
}
static void addPresentations(JNIEnv *env, const fields_t& fields,
+ const std::vector<TunerAudioPresentation>& tunerAudioPresentations,
+ jobject presentationsJObj) {
+ AudioPresentationCollection apc = {};
+ static const std::map<AudioPreselectionRenderingIndicationType, MasteringIndication> mMap {
+ { AudioPreselectionRenderingIndicationType::NOT_INDICATED, MASTERING_NOT_INDICATED },
+ { AudioPreselectionRenderingIndicationType::STEREO, MASTERED_FOR_STEREO },
+ { AudioPreselectionRenderingIndicationType::TWO_DIMENSIONAL, MASTERED_FOR_SURROUND },
+ { AudioPreselectionRenderingIndicationType::THREE_DIMENSIONAL, MASTERED_FOR_3D },
+ { AudioPreselectionRenderingIndicationType::HEADPHONE, MASTERED_FOR_HEADPHONE },
+ };
+ for (const auto& tap : tunerAudioPresentations) {
+ AudioPresentationV1 ap;
+ ap.mPresentationId = tap.preselection.preselectionId;
+ ap.mProgramId = tap.ac4ShortProgramId;
+ for (const auto& md : tap.preselection.labels) {
+ ap.mLabels.insert(std::pair(md.language, md.text));
+ }
+ ap.mLanguage = tap.preselection.language;
+ ap.mMasteringIndication = MASTERING_NOT_INDICATED;
+ auto masteringSearch = mMap.find(tap.preselection.renderingIndication);
+ if (masteringSearch != mMap.end()) {
+ ap.mMasteringIndication = masteringSearch->second;
+ }
+ ap.mAudioDescriptionAvailable = tap.preselection.hasAudioDescription;
+ ap.mSpokenSubtitlesAvailable = tap.preselection.hasSpokenSubtitles;
+ ap.mDialogueEnhancementAvailable = tap.preselection.hasDialogueEnhancement;
+ apc.push_back(ap);
+ }
+ addPresentations(env, fields, apc, presentationsJObj);
+ }
+
+ static void addPresentations(JNIEnv *env, const fields_t& fields,
const AudioPresentationCollection& presentations, jobject presentationsJObj) {
for (const auto& ap : presentations) {
ScopedLocalRef<jobject> jLabelObject = convertLabelsToMap(env, fields, ap.mLabels);
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 625e842..f56e236 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -20,6 +20,7 @@
#include "android_media_tv_Tuner.h"
#include <aidl/android/hardware/tv/tuner/AudioExtraMetaData.h>
+#include <aidl/android/hardware/tv/tuner/AudioPresentation.h>
#include <aidl/android/hardware/tv/tuner/AudioStreamType.h>
#include <aidl/android/hardware/tv/tuner/AvStreamType.h>
#include <aidl/android/hardware/tv/tuner/Constant.h>
@@ -161,12 +162,14 @@
#include <nativehelper/ScopedLocalRef.h>
#include <utils/Log.h>
+#include "android_media_AudioPresentation.h"
#include "android_media_MediaCodecLinearBlock.h"
#include "android_runtime/AndroidRuntime.h"
#pragma GCC diagnostic ignored "-Wunused-function"
using ::aidl::android::hardware::tv::tuner::AudioExtraMetaData;
+using ::aidl::android::hardware::tv::tuner::AudioPreselection;
using ::aidl::android::hardware::tv::tuner::AudioStreamType;
using ::aidl::android::hardware::tv::tuner::AvStreamType;
using ::aidl::android::hardware::tv::tuner::Constant;
@@ -362,6 +365,9 @@
}
namespace android {
+
+static JAudioPresentationInfo::fields_t gAudioPresentationFields;
+
/////////////// LnbClientCallbackImpl ///////////////////////
void LnbClientCallbackImpl::onEvent(const LnbEventType lnbEventType) {
ALOGV("LnbClientCallbackImpl::onEvent, type=%d", lnbEventType);
@@ -634,27 +640,45 @@
eventClazz,
"<init>",
"(IZJZJJJLandroid/media/MediaCodec$LinearBlock;"
- "ZJIZILandroid/media/tv/tuner/filter/AudioDescriptor;)V");
+ "ZJIZILandroid/media/tv/tuner/filter/AudioDescriptor;"
+ "Ljava/util/List;)V");
jfieldID eventContext = env->GetFieldID(eventClazz, "mNativeContext", "J");
const DemuxFilterMediaEvent &mediaEvent = event.get<DemuxFilterEvent::Tag::media>();
jobject audioDescriptor = nullptr;
- if (mediaEvent.extraMetaData.getTag() == DemuxFilterMediaEventExtraMetaData::Tag::audio) {
- jclass adClazz = env->FindClass("android/media/tv/tuner/filter/AudioDescriptor");
- jmethodID adInit = env->GetMethodID(adClazz, "<init>", "(BBCBBB)V");
+ gAudioPresentationFields.init(env);
+ jobject presentationsJObj = JAudioPresentationInfo::asJobject(env, gAudioPresentationFields);
+ switch (mediaEvent.extraMetaData.getTag()) {
+ case DemuxFilterMediaEventExtraMetaData::Tag::audio: {
+ jclass adClazz = env->FindClass("android/media/tv/tuner/filter/AudioDescriptor");
+ jmethodID adInit = env->GetMethodID(adClazz, "<init>", "(BBCBBB)V");
- const AudioExtraMetaData &ad =
- mediaEvent.extraMetaData.get<DemuxFilterMediaEventExtraMetaData::Tag::audio>();
- jbyte adFade = ad.adFade;
- jbyte adPan = ad.adPan;
- jchar versionTextTag = ad.versionTextTag;
- jbyte adGainCenter = ad.adGainCenter;
- jbyte adGainFront = ad.adGainFront;
- jbyte adGainSurround = ad.adGainSurround;
+ const AudioExtraMetaData &ad =
+ mediaEvent.extraMetaData.get<DemuxFilterMediaEventExtraMetaData::Tag::audio>();
+ jbyte adFade = ad.adFade;
+ jbyte adPan = ad.adPan;
+ jchar versionTextTag = ad.versionTextTag;
+ jbyte adGainCenter = ad.adGainCenter;
+ jbyte adGainFront = ad.adGainFront;
+ jbyte adGainSurround = ad.adGainSurround;
- audioDescriptor = env->NewObject(adClazz, adInit, adFade, adPan, versionTextTag,
- adGainCenter, adGainFront, adGainSurround);
- env->DeleteLocalRef(adClazz);
+ audioDescriptor = env->NewObject(adClazz, adInit, adFade, adPan, versionTextTag,
+ adGainCenter, adGainFront, adGainSurround);
+ env->DeleteLocalRef(adClazz);
+ break;
+ }
+ case DemuxFilterMediaEventExtraMetaData::Tag::audioPresentations: {
+ JAudioPresentationInfo::addPresentations(
+ env, gAudioPresentationFields,
+ mediaEvent.extraMetaData
+ .get<DemuxFilterMediaEventExtraMetaData::Tag::audioPresentations>(),
+ presentationsJObj);
+ break;
+ }
+ default: {
+ ALOGE("FilterClientCallbackImpl::getMediaEvent: unknown extraMetaData");
+ break;
+ }
}
jlong dataLength = mediaEvent.dataLength;
@@ -683,7 +707,8 @@
jobject obj = env->NewObject(eventClazz, eventInit, streamId, isPtsPresent, pts, isDtsPresent,
dts, dataLength, offset, nullptr, isSecureMemory, avDataId,
- mpuSequenceNumber, isPesPrivateData, sc, audioDescriptor);
+ mpuSequenceNumber, isPesPrivateData, sc, audioDescriptor,
+ presentationsJObj);
uint64_t avSharedMemSize = mFilterClient->getAvSharedHandleInfo().size;
if (mediaEvent.avMemory.fds.size() > 0 || mediaEvent.avDataId != 0 ||
@@ -702,6 +727,7 @@
}
env->DeleteLocalRef(obj);
env->DeleteLocalRef(eventClazz);
+ env->DeleteLocalRef(presentationsJObj);
}
void FilterClientCallbackImpl::getPesEvent(jobjectArray &arr, const int size,
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 27666caa..b3628fa 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -69,7 +69,7 @@
int updateTargetWorkDuration(int64_t targetDurationNanos);
int reportActualWorkDuration(int64_t actualDurationNanos);
- int sendHint(int32_t hint);
+ int sendHint(SessionHint hint);
int setThreads(const int32_t* threadIds, size_t size);
int getThreadIds(int32_t* const threadIds, size_t* size);
@@ -243,7 +243,7 @@
return 0;
}
-int APerformanceHintSession::sendHint(int32_t hint) {
+int APerformanceHintSession::sendHint(SessionHint hint) {
if (hint < 0 || hint >= static_cast<int32_t>(mLastHintSentTimestamp.size())) {
ALOGE("%s: invalid session hint %d", __FUNCTION__, hint);
return EINVAL;
@@ -335,7 +335,7 @@
delete session;
}
-int APerformanceHint_sendHint(void* session, int32_t hint) {
+int APerformanceHint_sendHint(void* session, SessionHint hint) {
return reinterpret_cast<APerformanceHintSession*>(session)->sendHint(hint);
}
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 321a7dd..791adfd 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -127,7 +127,7 @@
result = APerformanceHint_reportActualWorkDuration(session, -1L);
EXPECT_EQ(EINVAL, result);
- int hintId = 2;
+ SessionHint hintId = SessionHint::CPU_LOAD_RESET;
EXPECT_CALL(*iSession, sendHint(Eq(hintId))).Times(Exactly(1));
result = APerformanceHint_sendHint(session, hintId);
EXPECT_EQ(0, result);
@@ -140,7 +140,7 @@
result = APerformanceHint_sendHint(session, hintId);
EXPECT_EQ(0, result);
- result = APerformanceHint_sendHint(session, -1);
+ result = APerformanceHint_sendHint(session, static_cast<SessionHint>(-1));
EXPECT_EQ(EINVAL, result);
EXPECT_CALL(*iSession, close()).Times(Exactly(1));
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index a48cd2b..7b53e26 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -24,7 +24,7 @@
import android.content.pm.SigningInfo
import android.credentials.CreateCredentialRequest
import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
-import android.credentials.GetCredentialOption
+import android.credentials.CredentialOption
import android.credentials.GetCredentialRequest
import android.credentials.ui.Constants
import android.credentials.ui.Entry
@@ -496,8 +496,8 @@
GetCredentialRequest.Builder(
Bundle()
)
- .addGetCredentialOption(
- GetCredentialOption(
+ .addCredentialOption(
+ CredentialOption(
TYPE_PUBLIC_KEY_CREDENTIAL,
Bundle(),
Bundle(), /*isSystemProviderRequired=*/
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
index 5cb8d3b..fdd57ff 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
@@ -61,7 +61,7 @@
@JvmStatic
fun createFrom(from: android.credentials.GetCredentialRequest): GetCredentialRequest {
return GetCredentialRequest(
- from.getCredentialOptions.map {
+ from.credentialOptions.map {
GetCredentialOption.createFrom(
it.type,
it.credentialRetrievalData,
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index ae6f71c..ab680b0 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -38,10 +38,11 @@
<!-- Message for updating an existing app [CHAR LIMIT=NONE] -->
<string name="install_confirm_question_update">Do you want to update this app?</string>
<!-- TODO(b/244413073) Revise the description after getting UX input and UXR on this. -->
- <!-- Message for updating an existing app when updating owner changed [CHAR LIMIT=NONE] -->
+ <!-- Message for updating an existing app when updating owner changed [DO NOT TRANSLATE][CHAR LIMIT=NONE] -->
<string name="install_confirm_question_update_owner_changed">Updates to this app are currently managed by <xliff:g id="existing_update_owner">%1$s</xliff:g>.\n\nBy updating, you\'ll get future updates from <xliff:g id="new_update_owner">%2$s</xliff:g> instead.</string>
- <!-- Message for updating an existing app with update owner reminder [CHAR LIMIT=NONE] -->
- <string name="install_confirm_question_update_owner_reminder">Updates to this app are currently managed by <xliff:g id="existing_update_owner">%1$s</xliff:g>.\n\nDo you want to install this update from <xliff:g id="new_update_owner">%2$s</xliff:g>.</string>
+ <!-- TODO(b/244413073) Revise the description after getting UX input and UXR on this. -->
+ <!-- Message for updating an existing app with update owner reminder [DO NOT TRANSLATE][CHAR LIMIT=NONE] -->
+ <string name="install_confirm_question_update_owner_reminder">Updates to this app are currently managed by <xliff:g id="existing_update_owner">%1$s</xliff:g>.\n\nDo you want to install this update from <xliff:g id="new_update_owner">%2$s</xliff:g>?</string>
<!-- [CHAR LIMIT=100] -->
<string name="install_failed">App not installed.</string>
<!-- Reason displayed when installation fails because the package was blocked
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
old mode 100755
new mode 100644
index 93387e2..c6217ec
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -141,7 +141,7 @@
File file = new File(mPackageURI.getPath());
try {
final InstallInfo result = getPackageManager().getPackageInstaller()
- .getInstallInfo(file, 0);
+ .readInstallInfo(file, 0);
params.setAppPackageName(result.getPackageName());
params.setInstallLocation(result.getInstallLocation());
try {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 49c9188..3ec81aa 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -392,7 +392,7 @@
final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
-1 /* defaultValue */);
final SessionInfo info = mInstaller.getSessionInfo(sessionId);
- if (info == null || !info.getIsPreApprovalRequested()) {
+ if (info == null || !info.isPreApprovalRequested()) {
Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
finish();
return;
diff --git a/packages/SettingsLib/IllustrationPreference/res/values/attrs.xml b/packages/SettingsLib/IllustrationPreference/res/values/attrs.xml
new file mode 100644
index 0000000..141886c
--- /dev/null
+++ b/packages/SettingsLib/IllustrationPreference/res/values/attrs.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.
+-->
+
+<resources>
+ <declare-styleable name="IllustrationPreference">
+ <attr name="dynamicColor" format="boolean" />
+ </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index 1592094..3b90275 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -61,6 +61,8 @@
private View mMiddleGroundView;
private OnBindListener mOnBindListener;
+ private boolean mLottieDynamicColor;
+
/**
* Interface to listen in on when {@link #onBindViewHolder(PreferenceViewHolder)} occurs.
*/
@@ -146,6 +148,10 @@
ColorUtils.applyDynamicColors(getContext(), illustrationView);
}
+ if (mLottieDynamicColor) {
+ LottieColorUtils.applyDynamicColors(getContext(), illustrationView);
+ }
+
if (mOnBindListener != null) {
mOnBindListener.onBind(illustrationView);
}
@@ -262,6 +268,21 @@
}
}
+ /**
+ * Sets the lottie illustration apply dynamic color.
+ */
+ public void applyDynamicColor() {
+ mLottieDynamicColor = true;
+ notifyChanged();
+ }
+
+ /**
+ * Return if the lottie illustration apply dynamic color or not.
+ */
+ public boolean isApplyDynamicColor() {
+ return mLottieDynamicColor;
+ }
+
private void resetImageResourceCache() {
mImageDrawable = null;
mImageUri = null;
@@ -403,9 +424,15 @@
mIsAutoScale = false;
if (attrs != null) {
- final TypedArray a = context.obtainStyledAttributes(attrs,
+ TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.LottieAnimationView, 0 /*defStyleAttr*/, 0 /*defStyleRes*/);
mImageResId = a.getResourceId(R.styleable.LottieAnimationView_lottie_rawRes, 0);
+
+ a = context.obtainStyledAttributes(attrs,
+ R.styleable.IllustrationPreference, 0 /*defStyleAttr*/, 0 /*defStyleRes*/);
+ mLottieDynamicColor = a.getBoolean(R.styleable.IllustrationPreference_dynamicColor,
+ false);
+
a.recycle();
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index e54e276..c702f59 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -24,6 +24,9 @@
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
import com.android.settingslib.spa.gallery.dialog.AlterDialogPageProvider
import com.android.settingslib.spa.gallery.home.HomePageProvider
+import com.android.settingslib.spa.gallery.itemList.ItemListPageProvider
+import com.android.settingslib.spa.gallery.itemList.ItemOperatePageProvider
+import com.android.settingslib.spa.gallery.itemList.OperateListPageProvider
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
import com.android.settingslib.spa.gallery.page.ChartPageProvider
import com.android.settingslib.spa.gallery.page.FooterPageProvider
@@ -50,6 +53,8 @@
HOME("home"),
PREFERENCE("preference"),
ARGUMENT("argument"),
+ ITEM_LIST("itemList"),
+ ITEM_OP_PAGE("itemOp"),
// Add your SPPs
}
@@ -76,6 +81,9 @@
LoadingBarPageProvider,
ChartPageProvider,
AlterDialogPageProvider,
+ ItemListPageProvider,
+ ItemOperatePageProvider,
+ OperateListPageProvider,
),
rootPages = listOf(
HomePageProvider.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
index decc292..df1d7d1 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
@@ -24,8 +24,8 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.button.ActionButton
@@ -39,13 +39,24 @@
object ActionButtonPageProvider : SettingsPageProvider {
override val name = "ActionButton"
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
+ }
+
@Composable
override fun Page(arguments: Bundle?) {
- ActionButtonPage()
+ RegularScaffold(title = TITLE) {
+ val actionButtons = listOf(
+ ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+ ActionButton(text = "Uninstall", imageVector = Icons.Outlined.Delete) {},
+ ActionButton(text = "Force stop", imageVector = Icons.Outlined.WarningAmber) {},
+ )
+ ActionButtons(actionButtons)
+ }
}
fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+ return SettingsEntryBuilder.createInject(owner = createSettingsPage())
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
@@ -55,22 +66,10 @@
}
}
-@Composable
-fun ActionButtonPage() {
- RegularScaffold(title = TITLE) {
- val actionButtons = listOf(
- ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
- ActionButton(text = "Uninstall", imageVector = Icons.Outlined.Delete) {},
- ActionButton(text = "Force stop", imageVector = Icons.Outlined.WarningAmber) {},
- )
- ActionButtons(actionButtons)
- }
-}
-
@Preview(showBackground = true)
@Composable
private fun ActionButtonPagePreview() {
SettingsTheme {
- ActionButtonPage()
+ ActionButtonPageProvider.Page(null)
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
index 5d26b34..0878fc0 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
@@ -18,6 +18,7 @@
import android.os.Bundle
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -28,6 +29,7 @@
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
import com.android.settingslib.spa.gallery.dialog.AlterDialogPageProvider
+import com.android.settingslib.spa.gallery.itemList.OperateListPageProvider
import com.android.settingslib.spa.gallery.page.ArgumentPageModel
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
import com.android.settingslib.spa.gallery.page.ChartPageProvider
@@ -50,6 +52,7 @@
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
return listOf(
PreferenceMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ OperateListPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
ArgumentPageProvider.buildInjectEntry("foo")!!.setLink(fromPage = owner).build(),
SliderPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
SpinnerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
@@ -71,8 +74,10 @@
@Composable
override fun Page(arguments: Bundle?) {
- HomeScaffold(title = getTitle(arguments)) {
- for (entry in buildEntry(arguments)) {
+ val title = remember { getTitle(arguments) }
+ val entries = remember { buildEntry(arguments) }
+ HomeScaffold(title) {
+ for (entry in entries) {
if (entry.owner.isCreateBy(SettingsPageProviderEnum.ARGUMENT.name)) {
entry.UiLayout(ArgumentPageModel.buildArgument(intParam = 0))
} else {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemListPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemListPage.kt
new file mode 100644
index 0000000..08e6452
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemListPage.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.itemList
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.core.os.bundleOf
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import com.android.settingslib.spa.framework.common.EntrySearchData
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.util.getStringArg
+import com.android.settingslib.spa.framework.util.navLink
+import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+
+private const val OPERATOR_PARAM_NAME = "opParam"
+
+object ItemListPageProvider : SettingsPageProvider {
+ override val name = SettingsPageProviderEnum.ITEM_LIST.name
+ override val displayName = SettingsPageProviderEnum.ITEM_LIST.displayName
+ override val parameter = listOf(
+ navArgument(OPERATOR_PARAM_NAME) { type = NavType.StringType },
+ )
+
+ override fun getTitle(arguments: Bundle?): String {
+ val operation = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments) ?: "NULL"
+ return "Operation: $operation"
+ }
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ if (!ItemOperatePageProvider.isValidArgs(arguments)) return emptyList()
+ val operation = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments)!!
+ val owner = createSettingsPage(arguments)
+ return listOf(
+ ItemOperatePageProvider.buildInjectEntry(operation)!!.setLink(fromPage = owner).build(),
+ )
+ }
+
+ fun buildInjectEntry(opParam: String): SettingsEntryBuilder? {
+ val arguments = bundleOf(OPERATOR_PARAM_NAME to opParam)
+ if (!ItemOperatePageProvider.isValidArgs(arguments)) return null
+
+ return SettingsEntryBuilder.createInject(
+ owner = createSettingsPage(arguments),
+ displayName = "ItemList_$opParam",
+ ).setUiLayoutFn {
+ Preference(
+ object : PreferenceModel {
+ override val title = opParam
+ override val onClick = navigator(
+ SettingsPageProviderEnum.ITEM_LIST.name + parameter.navLink(it)
+ )
+ }
+ )
+ }.setSearchDataFn {
+ EntrySearchData(title = "Operation: $opParam")
+ }
+ }
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ val title = remember { getTitle(arguments) }
+ val entries = remember { buildEntry(arguments) }
+ val itemList = remember {
+ // Add logic to get item List during runtime.
+ listOf("itemFoo", "itemBar", "itemToy")
+ }
+ RegularScaffold(title) {
+ for (item in itemList) {
+ val rtArgs = ItemOperatePageProvider.genRuntimeArguments(item)
+ for (entry in entries) {
+ entry.UiLayout(rtArgs)
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemOperatePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemOperatePage.kt
new file mode 100644
index 0000000..8179356
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemOperatePage.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.itemList
+
+import android.os.Bundle
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.core.os.bundleOf
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.util.getStringArg
+import com.android.settingslib.spa.framework.util.navLink
+import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.SwitchPreference
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+
+private const val OPERATOR_PARAM_NAME = "opParam"
+private const val ITEM_NAME_PARAM_NAME = "rt_nameParam"
+private val ALLOWED_OPERATOR_LIST = listOf("opDnD", "opPiP", "opInstall", "opConnect")
+
+object ItemOperatePageProvider : SettingsPageProvider {
+ override val name = SettingsPageProviderEnum.ITEM_OP_PAGE.name
+ override val displayName = SettingsPageProviderEnum.ITEM_OP_PAGE.displayName
+ override val parameter = listOf(
+ navArgument(OPERATOR_PARAM_NAME) { type = NavType.StringType },
+ navArgument(ITEM_NAME_PARAM_NAME) { type = NavType.StringType },
+ )
+
+ override fun getTitle(arguments: Bundle?): String {
+ // Operation name is not a runtime parameter, which should always available
+ val operation = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments) ?: "opInValid"
+ // Item name is a runtime parameter, which could be missing
+ val itemName = parameter.getStringArg(ITEM_NAME_PARAM_NAME, arguments) ?: "[unset]"
+ return "$operation on $itemName"
+ }
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ if (!isValidArgs(arguments)) return emptyList()
+
+ val owner = createSettingsPage(arguments)
+ val entryList = mutableListOf<SettingsEntry>()
+ entryList.add(
+ SettingsEntryBuilder.create("ItemName", owner)
+ .setUiLayoutFn {
+ // Item name is a runtime parameter, which needs to be read inside UiLayoutFn
+ val itemName = parameter.getStringArg(ITEM_NAME_PARAM_NAME, it) ?: "NULL"
+ Preference(
+ object : PreferenceModel {
+ override val title = "Item $itemName"
+ }
+ )
+ }.build()
+ )
+
+ // Operation name is not a runtime parameter, which can be read outside.
+ val opName = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments)!!
+ entryList.add(
+ SettingsEntryBuilder.create("ItemOp", owner)
+ .setUiLayoutFn {
+ val checked = rememberSaveable { mutableStateOf(false) }
+ SwitchPreference(remember {
+ object : SwitchPreferenceModel {
+ override val title = "Item operation: $opName"
+ override val checked = checked
+ override val onCheckedChange =
+ { newChecked: Boolean -> checked.value = newChecked }
+ }
+ })
+ }.build(),
+ )
+ return entryList
+ }
+
+ fun buildInjectEntry(opParam: String): SettingsEntryBuilder? {
+ val arguments = bundleOf(OPERATOR_PARAM_NAME to opParam)
+ if (!isValidArgs(arguments)) return null
+
+ return SettingsEntryBuilder.createInject(
+ owner = createSettingsPage(arguments),
+ displayName = "ItemOp_$opParam",
+ ).setUiLayoutFn {
+ // Item name is a runtime parameter, which needs to be read inside UiLayoutFn
+ val itemName = parameter.getStringArg(ITEM_NAME_PARAM_NAME, it) ?: "NULL"
+ Preference(
+ object : PreferenceModel {
+ override val title = "item: $itemName"
+ override val onClick = navigator(
+ SettingsPageProviderEnum.ITEM_OP_PAGE.name + parameter.navLink(it)
+ )
+ }
+ )
+ }
+ }
+
+ fun isValidArgs(arguments: Bundle?): Boolean {
+ val opParam = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments)
+ return (opParam != null && ALLOWED_OPERATOR_LIST.contains(opParam))
+ }
+
+ fun genRuntimeArguments(itemName: String): Bundle {
+ return bundleOf(ITEM_NAME_PARAM_NAME to itemName)
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/OperateListPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/OperateListPage.kt
new file mode 100644
index 0000000..e0baf86
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/OperateListPage.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.itemList
+
+import android.os.Bundle
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+
+private const val TITLE = "Operate List Main"
+
+object OperateListPageProvider : SettingsPageProvider {
+ override val name = "OpList"
+ private val owner = createSettingsPage()
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ return listOf(
+ ItemListPageProvider.buildInjectEntry("opPiP")!!.setLink(fromPage = owner).build(),
+ ItemListPageProvider.buildInjectEntry("opInstall")!!.setLink(fromPage = owner).build(),
+ ItemListPageProvider.buildInjectEntry("opDnD")!!.setLink(fromPage = owner).build(),
+ ItemListPageProvider.buildInjectEntry("opConnect")!!.setLink(fromPage = owner).build(),
+ )
+ }
+
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
+ }
+
+ fun buildInjectEntry(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(owner = owner)
+ .setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index 42ac1ac..eca47b6 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -18,6 +18,7 @@
import android.os.Bundle
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
@@ -101,10 +102,13 @@
@Composable
override fun Page(arguments: Bundle?) {
- RegularScaffold(title = getTitle(arguments)) {
- for (entry in buildEntry(arguments)) {
+ val title = remember { getTitle(arguments) }
+ val entries = remember { buildEntry(arguments) }
+ val rtArgNext = remember { ArgumentPageModel.buildNextArgument(arguments) }
+ RegularScaffold(title) {
+ for (entry in entries) {
if (entry.toPage != null) {
- entry.UiLayout(ArgumentPageModel.buildNextArgument(arguments))
+ entry.UiLayout(rtArgNext)
} else {
entry.UiLayout()
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
index 5f15865..5d6aa03 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
@@ -132,7 +132,7 @@
override val title = PAGE_TITLE
override val summary = stateOf(summaryArray.joinToString(", "))
override val onClick = navigator(
- SettingsPageProviderEnum.ARGUMENT.displayName + parameter.navLink(arguments)
+ SettingsPageProviderEnum.ARGUMENT.name + parameter.navLink(arguments)
)
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
index 7f21a4d..eeab085 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
@@ -21,8 +21,8 @@
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.chart.BarChart
@@ -36,7 +36,6 @@
import com.android.settingslib.spa.widget.chart.PieChartModel
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.github.mikephil.charting.formatter.IAxisValueFormatter
import java.text.NumberFormat
@@ -47,9 +46,13 @@
object ChartPageProvider : SettingsPageProvider {
override val name = "Chart"
+ val owner = createSettingsPage()
+
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
+ }
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- val owner = SettingsPage.create(name)
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
SettingsEntryBuilder.create("Line Chart", owner)
@@ -133,7 +136,7 @@
}
fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+ return SettingsEntryBuilder.createInject(owner)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
@@ -141,15 +144,6 @@
})
}
}
-
- @Composable
- override fun Page(arguments: Bundle?) {
- RegularScaffold(title = TITLE) {
- for (entry in buildEntry(arguments)) {
- entry.UiLayout(arguments)
- }
- }
- }
}
@Preview(showBackground = true)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
index 9f24ea9..2328fcb 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
@@ -23,8 +23,8 @@
import com.android.settingslib.spa.framework.common.EntrySearchData
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -37,9 +37,9 @@
object FooterPageProvider : SettingsPageProvider {
override val name = "Footer"
+ val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- val owner = SettingsPage.create(name)
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
SettingsEntryBuilder.create( "Some Preference", owner)
@@ -58,7 +58,7 @@
}
fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+ return SettingsEntryBuilder.createInject(owner)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
index 44f0343..45b7989 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
@@ -21,8 +21,8 @@
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.R
@@ -36,9 +36,9 @@
object IllustrationPageProvider : SettingsPageProvider {
override val name = "Illustration"
+ val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- val owner = SettingsPage.create(name)
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
SettingsEntryBuilder.create( "Lottie Illustration", owner)
@@ -71,7 +71,7 @@
}
fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+ return SettingsEntryBuilder.createInject(owner)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPage.kt
index 4332a81..247990c 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPage.kt
@@ -29,8 +29,8 @@
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
@@ -45,7 +45,7 @@
override val name = "LoadingBar"
fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+ return SettingsEntryBuilder.createInject(owner = createSettingsPage())
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
index 20d90dd..9026a24 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
@@ -28,8 +28,8 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
@@ -47,7 +47,7 @@
override val name = "ProgressBar"
fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+ return SettingsEntryBuilder.createInject(owner = createSettingsPage())
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
index c0d0abc..dc45e6d 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
@@ -23,8 +23,8 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
@@ -39,7 +39,7 @@
override val name = "SettingsPager"
fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+ return SettingsEntryBuilder.createInject(owner = createSettingsPage())
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
index a62ec7b..74e49a6 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
@@ -29,8 +29,8 @@
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.SliderPreference
@@ -42,9 +42,9 @@
object SliderPageProvider : SettingsPageProvider {
override val name = "Slider"
+ val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- val owner = SettingsPage.create(name)
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
SettingsEntryBuilder.create("Simple Slider", owner)
@@ -104,7 +104,7 @@
}
fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+ return SettingsEntryBuilder.createInject(owner)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
index 67e35dc..d100d9d 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
@@ -24,8 +24,8 @@
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -38,9 +38,9 @@
object MainSwitchPreferencePageProvider : SettingsPageProvider {
override val name = "MainSwitchPreference"
+ val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- val owner = SettingsPage.create(name)
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
SettingsEntryBuilder.create( "MainSwitchPreference", owner)
@@ -59,7 +59,7 @@
}
fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+ return SettingsEntryBuilder.createInject(owner)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
index 067911c..6ad4bd8 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
@@ -27,8 +27,8 @@
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -43,9 +43,9 @@
object SwitchPreferencePageProvider : SettingsPageProvider {
override val name = "SwitchPreference"
+ val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- val owner = SettingsPage.create(name)
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
SettingsEntryBuilder.create( "SwitchPreference", owner)
@@ -82,7 +82,7 @@
}
fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+ return SettingsEntryBuilder.createInject(owner)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
index 33e5e8d..770f9a0 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
@@ -25,8 +25,8 @@
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -40,9 +40,9 @@
object TwoTargetSwitchPreferencePageProvider : SettingsPageProvider {
override val name = "TwoTargetSwitchPreference"
+ val owner = createSettingsPage()
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- val owner = SettingsPage.create(name)
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
SettingsEntryBuilder.create( "TwoTargetSwitchPreference", owner)
@@ -73,7 +73,7 @@
}
fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+ return SettingsEntryBuilder.createInject(owner)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
index cb58abf6..7a1fad0 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
@@ -18,16 +18,17 @@
import android.os.Bundle
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.EntrySearchData
+import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spa.widget.ui.Category
import com.android.settingslib.spa.widget.ui.CategoryTitle
@@ -36,50 +37,60 @@
object CategoryPageProvider : SettingsPageProvider {
override val name = "Category"
+ private val owner = createSettingsPage()
fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+ return SettingsEntryBuilder.createInject(owner)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
override val onClick = navigator(name)
})
}
+ .setSearchDataFn { EntrySearchData(title = TITLE) }
}
override fun getTitle(arguments: Bundle?): String {
return TITLE
}
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val entryList = mutableListOf<SettingsEntry>()
+ entryList.add(
+ SettingsEntryBuilder.create("Preference 1", owner)
+ .setMacro { SimplePreferenceMacro(title = "Preference 1", summary = "Summary 1") }
+ .build()
+ )
+ entryList.add(
+ SettingsEntryBuilder.create("Preference 2", owner)
+ .setMacro { SimplePreferenceMacro(title = "Preference 2", summary = "Summary 2") }
+ .build()
+ )
+ entryList.add(
+ SettingsEntryBuilder.create("Preference 3", owner)
+ .setMacro { SimplePreferenceMacro(title = "Preference 2", summary = "Summary 3") }
+ .build()
+
+ )
+ entryList.add(
+ SettingsEntryBuilder.create("Preference 4", owner)
+ .setMacro { SimplePreferenceMacro(title = "Preference 4", summary = "Summary 4") }
+ .build()
+ )
+ return entryList
+ }
+
@Composable
override fun Page(arguments: Bundle?) {
+ val entries = buildEntry(arguments)
RegularScaffold(title = getTitle(arguments)) {
CategoryTitle("Category A")
- Preference(remember {
- object : PreferenceModel {
- override val title = "Preference 1"
- override val summary = stateOf("Summary 1")
- }
- })
- Preference(remember {
- object : PreferenceModel {
- override val title = "Preference 2"
- override val summary = stateOf("Summary 2")
- }
- })
+ entries[0].UiLayout()
+ entries[1].UiLayout()
+
Category("Category B") {
- Preference(remember {
- object : PreferenceModel {
- override val title = "Preference 3"
- override val summary = stateOf("Summary 3")
- }
- })
- Preference(remember {
- object : PreferenceModel {
- override val title = "Preference 4"
- override val summary = stateOf("Summary 4")
- }
- })
+ entries[2].UiLayout()
+ entries[3].UiLayout()
}
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
index 8fdc22f..aeba6ea 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
@@ -26,8 +26,8 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
@@ -42,7 +42,7 @@
override val name = "Spinner"
fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+ return SettingsEntryBuilder.createInject(owner = createSettingsPage())
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = TITLE
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
index 72f662f..078c925 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
@@ -175,6 +175,7 @@
RegularScaffold(title = "Page - ${page.debugBrief()}") {
Text(text = "id = ${page.id}")
Text(text = page.debugArguments())
+ Text(text = "enabled = ${page.isEnabled()}")
Text(text = "Entry size: ${pageWithEntry.entries.size}")
Preference(model = object : PreferenceModel {
override val title = "open page"
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
index 6ecf9c3..d95ed05 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
@@ -66,6 +66,11 @@
"linkFrom = ${fromPage?.debugBrief()} ${fromPage?.debugArguments()}",
"linkTo = ${toPage?.debugBrief()} ${toPage?.debugArguments()}",
"hierarchy_path = $entryPathWithName",
+ "------ ATTRIBUTION ------",
+ "allowSearch = $isAllowSearch",
+ "isSearchDynamic = $isSearchDataDynamic",
+ "isSearchMutable = $hasMutableStatus",
+ "hasSlice = $hasSliceSupport",
"------ SEARCH ------",
"search_path = $entryPathWithTitle",
searchData?.debugContent() ?: "no search data",
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index 4d8b89b..bccd8aa 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -32,6 +32,7 @@
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.unit.IntOffset
import androidx.core.view.WindowCompat
@@ -42,6 +43,7 @@
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.AnimatedNavHost
import com.android.settingslib.spa.framework.compose.LocalNavController
import com.android.settingslib.spa.framework.compose.NavControllerWrapperImpl
@@ -136,7 +138,10 @@
AnimatedContentScope.SlideDirection.Right, animationSpec = slideEffect
) + fadeOut(animationSpec = fadeEffect)
},
- ) { navBackStackEntry -> spp.PageWithEvent(navBackStackEntry.arguments) }
+ ) { navBackStackEntry ->
+ val page = remember { spp.createSettingsPage(navBackStackEntry.arguments) }
+ page.PageWithEvent()
+ }
}
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 2175e55..44714ab 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -198,6 +198,7 @@
toPage = toPage,
// attributes
+ // TODO: set isEnabled & (isAllowSearch, hasSliceSupport) separately
isAllowSearch = isEnabled && isAllowSearch,
isSearchDataDynamic = isSearchDataDynamic,
hasMutableStatus = hasMutableStatus,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index a362877..94cfcc2 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -17,10 +17,12 @@
package com.android.settingslib.spa.framework.common
import android.os.Bundle
+import androidx.compose.runtime.Composable
import androidx.navigation.NamedNavArgument
import com.android.settingslib.spa.framework.util.isRuntimeParam
import com.android.settingslib.spa.framework.util.navLink
import com.android.settingslib.spa.framework.util.normalize
+import com.android.settingslib.spa.framework.util.normalizeArgList
private const val NULL_PAGE_NAME = "NULL"
@@ -91,23 +93,32 @@
}
fun isBrowsable(): Boolean {
- return !isCreateBy(NULL_PAGE_NAME) &&
- !hasRuntimeParam()
+ return !isCreateBy(NULL_PAGE_NAME) && !hasRuntimeParam()
+ }
+
+ private fun getProvider(): SettingsPageProvider? {
+ if (!SpaEnvironmentFactory.isReady()) return null
+ val pageProviderRepository by SpaEnvironmentFactory.instance.pageProviderRepository
+ return pageProviderRepository.getProviderOrNull(sppName)
}
fun isEnabled(): Boolean {
- if (!SpaEnvironmentFactory.isReady()) return false
- val pageProviderRepository by SpaEnvironmentFactory.instance.pageProviderRepository
- return pageProviderRepository.getProviderOrNull(sppName)?.isEnabled(arguments) ?: false
+ return getProvider()?.isEnabled(arguments) ?: false
+ }
+
+ @Composable
+ fun UiLayout() {
+ getProvider()?.Page(arguments)
}
}
fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
return SettingsPage.create(
name = name,
- displayName = displayName,
+ displayName = displayName + parameter.normalizeArgList(arguments, eraseRuntimeValues = true)
+ .joinToString("") { arg -> "/$arg" },
parameter = parameter,
- arguments = arguments
+ arguments = arguments,
)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 42e5f7e..c564130 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -18,6 +18,7 @@
import android.os.Bundle
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.navigation.NamedNavArgument
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
@@ -52,8 +53,10 @@
/** The [Composable] used to render this page. */
@Composable
fun Page(arguments: Bundle?) {
- RegularScaffold(title = getTitle(arguments)) {
- for (entry in buildEntry(arguments)) {
+ val title = remember { getTitle(arguments) }
+ val entries = remember { buildEntry(arguments) }
+ RegularScaffold(title) {
+ for (entry in entries) {
entry.UiLayout()
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
index f672ee0..02962a5 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -71,11 +71,15 @@
val sliceDataRepository = lazy { SettingsSliceDataRepository(entryRepository.value) }
- // In Robolectric test, applicationContext is not available. Use context as fallback.
+ // The application context. Use local context as fallback when applicationContext is not
+ // available (e.g. in Robolectric test).
val appContext: Context = context.applicationContext ?: context
+ // Set your SpaLogger implementation, for any SPA events logging.
open val logger: SpaLogger = object : SpaLogger {}
+ // Specify class name of browse activity and slice broadcast receiver, which is used to
+ // generate the necessary intents.
open val browseActivityClass: Class<out Activity>? = null
open val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? = null
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
index 22a4563..dde4e04 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
@@ -16,32 +16,27 @@
package com.android.settingslib.spa.framework.util
-import android.os.Bundle
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
import androidx.core.os.bundleOf
import com.android.settingslib.spa.framework.common.LOG_DATA_DISPLAY_NAME
import com.android.settingslib.spa.framework.common.LOG_DATA_SESSION_NAME
import com.android.settingslib.spa.framework.common.LogCategory
import com.android.settingslib.spa.framework.common.LogEvent
import com.android.settingslib.spa.framework.common.SettingsPage
-import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.LifecycleEffect
import com.android.settingslib.spa.framework.compose.LocalNavController
import com.android.settingslib.spa.framework.compose.NavControllerWrapper
@Composable
-internal fun SettingsPageProvider.PageWithEvent(arguments: Bundle? = null) {
- if (!isEnabled(arguments)) return
- val page = remember(arguments) { createSettingsPage(arguments) }
+internal fun SettingsPage.PageWithEvent() {
+ if (!isEnabled()) return
val navController = LocalNavController.current
LifecycleEffect(
- onStart = { page.logPageEvent(LogEvent.PAGE_ENTER, navController) },
- onStop = { page.logPageEvent(LogEvent.PAGE_LEAVE, navController) },
+ onStart = { logPageEvent(LogEvent.PAGE_ENTER, navController) },
+ onStop = { logPageEvent(LogEvent.PAGE_LEAVE, navController) },
)
- Page(arguments)
+ UiLayout()
}
private fun SettingsPage.logPageEvent(event: LogEvent, navController: NavControllerWrapper) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
index be303f0..f0eeb13 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
@@ -29,8 +29,16 @@
}
fun List<NamedNavArgument>.navLink(arguments: Bundle? = null): String {
+ return normalizeArgList(arguments).joinToString("") { arg -> "/$arg" }
+}
+
+fun List<NamedNavArgument>.normalizeArgList(
+ arguments: Bundle? = null,
+ eraseRuntimeValues: Boolean = false
+): List<String> {
val argsArray = mutableListOf<String>()
for (navArg in this) {
+ if (eraseRuntimeValues && navArg.isRuntimeParam()) continue
if (arguments == null || !arguments.containsKey(navArg.name)) {
argsArray.add(UNSET_PARAM_VALUE)
continue
@@ -44,7 +52,7 @@
}
}
}
- return argsArray.joinToString("") { arg -> "/$arg" }
+ return argsArray
}
fun List<NamedNavArgument>.normalize(
@@ -55,7 +63,7 @@
val normArgs = Bundle()
for (navArg in this) {
// Erase value of runtime parameters.
- if (navArg.isRuntimeParam() && eraseRuntimeValues) {
+ if (eraseRuntimeValues && navArg.isRuntimeParam()) {
normArgs.putString(navArg.name, null)
continue
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
index d6f5328..207c174 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
@@ -30,9 +30,9 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
@@ -74,7 +74,6 @@
return alertDialogPresenter
}
-@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun AlertDialogPresenter.SettingsAlertDialog(
confirmButton: AlertDialogButton?,
@@ -82,15 +81,9 @@
title: String?,
text: @Composable (() -> Unit)?,
) {
- val configuration = LocalConfiguration.current
AlertDialog(
onDismissRequest = ::close,
- modifier = when (configuration.orientation) {
- Configuration.ORIENTATION_LANDSCAPE -> {
- Modifier.width(configuration.screenWidthDp.dp * 0.6f)
- }
- else -> Modifier
- },
+ modifier = Modifier.width(getDialogWidth()),
confirmButton = { confirmButton?.let { Button(it) } },
dismissButton = dismissButton?.let { { Button(it) } },
title = title?.let { { Text(it) } },
@@ -106,6 +99,15 @@
}
@Composable
+private fun getDialogWidth(): Dp {
+ val configuration = LocalConfiguration.current
+ return configuration.screenWidthDp.dp * when (configuration.orientation) {
+ Configuration.ORIENTATION_LANDSCAPE -> 0.6f
+ else -> 0.8f
+ }
+}
+
+@Composable
private fun AlertDialogPresenter.Button(button: AlertDialogButton) {
TextButton(
onClick = {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
index 1f5de2d..7fa1e26 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
@@ -75,7 +75,7 @@
)
)
assertThat(page.sppName).isEqualTo("SppWithParam")
- assertThat(page.displayName).isEqualTo("SppWithParam")
+ assertThat(page.displayName).isEqualTo("SppWithParam/myStr/10")
assertThat(page.buildRoute()).isEqualTo("SppWithParam/myStr/10")
assertThat(page.isCreateBy("SppWithParam")).isTrue()
assertThat(page.hasRuntimeParam()).isFalse()
@@ -100,7 +100,7 @@
)
)
assertThat(page.sppName).isEqualTo("SppWithRtParam")
- assertThat(page.displayName).isEqualTo("SppWithRtParam")
+ assertThat(page.displayName).isEqualTo("SppWithRtParam/myStr/10")
assertThat(page.buildRoute()).isEqualTo("SppWithRtParam/myStr/10/rtStr")
assertThat(page.isCreateBy("SppWithRtParam")).isTrue()
assertThat(page.hasRuntimeParam()).isTrue()
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 8508878..c155433 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1315,6 +1315,13 @@
<string name="media_transfer_this_device_name" product="tablet">This tablet</string>
<!-- Name of the phone device with an active remote session. [CHAR LIMIT=30] -->
<string name="media_transfer_this_phone">This phone</string>
+ <!-- Sub status indicates device need premium account. [CHAR LIMIT=NONE] -->
+ <string name="media_output_status_require_premium">Upgrade account to switch</string>
+ <!-- Sub status indicates device not support download content. [CHAR LIMIT=NONE] -->
+ <string name="media_output_status_not_support_downloads">Can\’t play downloads here</string>
+ <!-- Sub status indicates device need to wait after ad. [CHAR LIMIT=NONE] -->
+ <string name="media_output_status_try_after_ad">Try again after the ad</string>
+
<!-- Warning message to tell user is have problem during profile connect, it need to turn off device and back on. [CHAR_LIMIT=NONE] -->
<string name="profile_connect_timeout_subtext">Problem connecting. Turn device off & back on</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 21e7d81..48d449d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -101,6 +101,9 @@
@VisibleForTesting
static ApplicationsState sInstance;
+ // Whether the app icon cache mechanism is enabled or not.
+ private static boolean sAppIconCacheEnabled = false;
+
public static ApplicationsState getInstance(Application app) {
return getInstance(app, AppGlobals.getPackageManager());
}
@@ -115,6 +118,11 @@
}
}
+ /** Set whether the app icon cache mechanism is enabled or not. */
+ public static void setAppIconCacheEnabled(boolean enabled) {
+ sAppIconCacheEnabled = enabled;
+ }
+
final Context mContext;
final PackageManager mPm;
final IPackageManager mIpm;
@@ -776,7 +784,8 @@
}
private static boolean isAppIconCacheEnabled(Context context) {
- return SETTING_PKG.equals(context.getPackageName());
+ return SETTING_PKG.equals(context.getPackageName())
+ || sAppIconCacheEnabled;
}
void rebuildActiveSessions() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 2bdbb16..d222b98 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -34,10 +34,12 @@
import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
+import android.annotation.Nullable;
import android.annotation.TargetApi;
import android.app.Notification;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.content.ComponentName;
import android.content.Context;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
@@ -191,6 +193,14 @@
&& Api34Impl.preferRouteListingOrdering(mRouterManager, mPackageName);
}
+ @Nullable
+ ComponentName getLinkedItemComponentName() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ return null;
+ }
+ return Api34Impl.getLinkedItemComponentName(mRouterManager, mPackageName);
+ }
+
/**
* Remove a {@code device} from current media.
*
@@ -682,6 +692,16 @@
}
@DoNotInline
+ @Nullable
+ static ComponentName getLinkedItemComponentName(
+ MediaRouter2Manager mediaRouter2Manager, String packageName) {
+ RouteListingPreference routeListingPreference =
+ mediaRouter2Manager.getRouteListingPreference(packageName);
+ return routeListingPreference == null ? null
+ : routeListingPreference.getLinkedItemComponentName();
+ }
+
+ @DoNotInline
static void onRouteListingPreferenceUpdated(
String packageName,
RouteListingPreference routeListingPreference,
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index aec1767..24acf8a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -20,6 +20,7 @@
import android.app.Notification;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.content.ComponentName;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
@@ -217,6 +218,16 @@
}
/**
+ * Returns required component name for system to take the user back to the app by launching an
+ * intent with the returned {@link ComponentName}, using action {@link #ACTION_TRANSFER_MEDIA},
+ * with the extra {@link #EXTRA_ROUTE_ID}.
+ */
+ @Nullable
+ public ComponentName getLinkedItemComponentName() {
+ return mInfoMediaManager.getLinkedItemComponentName();
+ }
+
+ /**
* Start scan connected MediaDevice
*/
public void startScan() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index d708e57..156993d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -30,8 +30,14 @@
import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
-import static android.media.RouteListingPreference.Item.DISABLE_REASON_NONE;
+import static android.media.RouteListingPreference.Item.FLAG_ONGOING_SESSION;
import static android.media.RouteListingPreference.Item.FLAG_SUGGESTED_ROUTE;
+import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_NONE;
+import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED;
+import static android.media.RouteListingPreference.Item.SUBTEXT_CUSTOM;
+import static android.media.RouteListingPreference.Item.SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED;
+import static android.media.RouteListingPreference.Item.SUBTEXT_NONE;
+import static android.media.RouteListingPreference.Item.SUBTEXT_SUBSCRIPTION_REQUIRED;
import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
@@ -51,6 +57,8 @@
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
+import com.android.settingslib.R;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -194,24 +202,56 @@
public abstract String getId();
/**
- * Get disabled reason of device
+ * Get selection behavior of device
*
- * @return disabled reason of device
+ * @return selection behavior of device
*/
- @RouteListingPreference.Item.DisableReason
- public int getDisableReason() {
+ @RouteListingPreference.Item.SubText
+ public int getSelectionBehavior() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null
- ? mItem.getDisableReason() : -1;
+ ? mItem.getSelectionBehavior() : SELECTION_BEHAVIOR_NONE;
}
/**
- * Checks if device is has disabled reason
+ * Checks if device is has subtext
*
- * @return true if device has disabled reason
+ * @return true if device has subtext
*/
- public boolean hasDisabledReason() {
+ public boolean hasSubtext() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+ && mItem != null
+ && mItem.getSubText() != SUBTEXT_NONE;
+ }
+
+ /**
+ * Get subtext of device
+ *
+ * @return subtext of device
+ */
+ @RouteListingPreference.Item.SubText
+ public int getSubtext() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null
- && mItem.getDisableReason() != DISABLE_REASON_NONE;
+ ? mItem.getSubText() : SUBTEXT_NONE;
+ }
+
+ /**
+ * Returns subtext string for current route.
+ *
+ * @return subtext string for this route
+ */
+ public String getSubtextString() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null
+ ? Api34Impl.composeSubtext(mItem, mContext) : null;
+ }
+
+ /**
+ * Checks if device has ongoing shared session, which allow user to join
+ *
+ * @return true if device has ongoing session
+ */
+ public boolean hasOngoingSession() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+ && Api34Impl.hasOngoingSession(mItem);
}
/**
@@ -511,7 +551,27 @@
private static class Api34Impl {
@DoNotInline
static boolean isSuggestedDevice(RouteListingPreference.Item item) {
- return item != null && item.getFlags() == FLAG_SUGGESTED_ROUTE;
+ return item != null && (item.getFlags() & FLAG_SUGGESTED_ROUTE) != 0;
+ }
+
+ @DoNotInline
+ static boolean hasOngoingSession(RouteListingPreference.Item item) {
+ return item != null && (item.getFlags() & FLAG_ONGOING_SESSION) != 0;
+ }
+
+ @DoNotInline
+ static String composeSubtext(RouteListingPreference.Item item, Context context) {
+ switch (item.getSubText()) {
+ case SUBTEXT_SUBSCRIPTION_REQUIRED:
+ return context.getString(R.string.media_output_status_require_premium);
+ case SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED:
+ return context.getString(R.string.media_output_status_not_support_downloads);
+ case SUBTEXT_AD_ROUTING_DISALLOWED:
+ return context.getString(R.string.media_output_status_try_after_ad);
+ case SUBTEXT_CUSTOM:
+ return (String) item.getCustomSubtextMessage();
+ }
+ return "";
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/OWNERS b/packages/SettingsLib/src/com/android/settingslib/media/OWNERS
index d40f322..3cae39f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/media/OWNERS
@@ -1,2 +1,5 @@
# Default reviewers for this and subdirectories.
shaoweishen@google.com
+
+#Android Media - For minor changes and renames only.
+aquilescanta@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 2c8aa26..31038cd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -35,6 +35,7 @@
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothDevice;
+import android.content.ComponentName;
import android.content.Context;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
@@ -88,6 +89,8 @@
private MediaManager.MediaDeviceCallback mCallback;
@Mock
private MediaSessionManager mMediaSessionManager;
+ @Mock
+ private ComponentName mComponentName;
private InfoMediaManager mInfoMediaManager;
private Context mContext;
@@ -372,6 +375,24 @@
assertThat(mInfoMediaManager.preferRouteListingOrdering()).isFalse();
}
+ @Test
+ public void getInAppOnlyItemRoutingReceiver_oldSdkVersion_returnsNull() {
+ assertThat(mInfoMediaManager.getLinkedItemComponentName()).isNull();
+ }
+
+ @Test
+ public void getInAppOnlyItemRoutingReceiver_newSdkVersionWithReceiverExist_returns() {
+ ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
+ Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
+ when(mRouterManager.getRouteListingPreference(any())).thenReturn(
+ new RouteListingPreference.Builder().setItems(
+ ImmutableList.of()).setUseSystemOrdering(
+ false).setLinkedItemComponentName(mComponentName).build());
+ mInfoMediaManager.mRouterManager = mRouterManager;
+
+ assertThat(mInfoMediaManager.getLinkedItemComponentName()).isEqualTo(mComponentName);
+ }
+
private List<MediaRoute2Info> getRoutesListWithDuplicatedIds() {
final List<MediaRoute2Info> routes = new ArrayList<>();
final MediaRoute2Info info = mock(MediaRoute2Info.class);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
index 103512d..21e119a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
@@ -215,4 +215,20 @@
assertThat(mOnBindListenerAnimationView).isNull();
}
+
+ @Test
+ public void onBindViewHolder_default_shouldNotApplyDynamicColor() {
+ mPreference.onBindViewHolder(mViewHolder);
+
+ assertThat(mPreference.isApplyDynamicColor()).isFalse();
+ }
+
+ @Test
+ public void onBindViewHolder_applyDynamicColor_shouldReturnTrue() {
+ mPreference.applyDynamicColor();
+
+ mPreference.onBindViewHolder(mViewHolder);
+
+ assertThat(mPreference.isApplyDynamicColor()).isTrue();
+ }
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index c0818a8..7cd8d70 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -99,5 +99,7 @@
Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_MS,
Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_SET_BY_USER,
Settings.Global.Wearable.DYNAMIC_COLOR_THEME_ENABLED,
+ Settings.Global.HDR_CONVERSION_MODE,
+ Settings.Global.HDR_FORCE_CONVERSION_TYPE,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/LargeScreenSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/LargeScreenSettings.java
new file mode 100644
index 0000000..bdd4869
--- /dev/null
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/LargeScreenSettings.java
@@ -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 android.provider.settings.backup;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
+
+/** Settings that should not be restored when target device is a large screen
+ * i.e. tablets and foldables in unfolded state
+ */
+public class LargeScreenSettings {
+ private static final float LARGE_SCREEN_MIN_DPS = 600;
+ private static final String LARGE_SCREEN_DO_NOT_RESTORE = "accelerometer_rotation";
+
+ /**
+ * Autorotation setting should not be restored when the target device is a large screen.
+ * (b/243489549)
+ */
+ public static boolean doNotRestoreIfLargeScreenSetting(String key, Context context) {
+ return isLargeScreen(context) && LARGE_SCREEN_DO_NOT_RESTORE.equals(key);
+ }
+
+ // copied from systemui/shared/...Utilities.java
+ // since we don't want to add compile time dependency on sys ui package
+ private static boolean isLargeScreen(Context context) {
+ final WindowManager windowManager = context.getSystemService(WindowManager.class);
+ final Rect bounds = windowManager.getCurrentWindowMetrics().getBounds();
+ float smallestWidth = dpiFromPx(Math.min(bounds.width(), bounds.height()),
+ context.getResources().getConfiguration().densityDpi);
+ return smallestWidth >= LARGE_SCREEN_MIN_DPS;
+ }
+
+ private static float dpiFromPx(float size, int densityDpi) {
+ float densityRatio = (float) densityDpi / DisplayMetrics.DENSITY_DEFAULT;
+ return (size / densityRatio);
+ }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 673a3ab..3ffbabd 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -16,6 +16,9 @@
package android.provider.settings.validators;
+import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_FORCE;
+import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_PASSTHROUGH;
+import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_SYSTEM;
import static android.media.AudioFormat.SURROUND_SOUND_ENCODING;
import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
@@ -26,6 +29,11 @@
import static android.provider.settings.validators.SettingsValidators.PACKAGE_NAME_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.PERCENTAGE_INTEGER_VALIDATOR;
import static android.view.Display.HdrCapabilities.HDR_TYPES;
+import static android.view.Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION;
+import static android.view.Display.HdrCapabilities.HDR_TYPE_HDR10;
+import static android.view.Display.HdrCapabilities.HDR_TYPE_HDR10_PLUS;
+import static android.view.Display.HdrCapabilities.HDR_TYPE_HLG;
+import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
import android.os.BatteryManager;
import android.provider.Settings.Global;
@@ -346,6 +354,20 @@
VALIDATORS.put(Global.USER_PREFERRED_REFRESH_RATE, NON_NEGATIVE_FLOAT_VALIDATOR);
VALIDATORS.put(Global.USER_PREFERRED_RESOLUTION_HEIGHT, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Global.USER_PREFERRED_RESOLUTION_WIDTH, ANY_INTEGER_VALIDATOR);
+ VALIDATORS.put(Global.HDR_CONVERSION_MODE, new DiscreteValueValidator(
+ new String[] {
+ String.valueOf(HDR_CONVERSION_PASSTHROUGH),
+ String.valueOf(HDR_CONVERSION_SYSTEM),
+ String.valueOf(HDR_CONVERSION_FORCE)
+ }));
+ VALIDATORS.put(Global.HDR_FORCE_CONVERSION_TYPE, new DiscreteValueValidator(
+ new String[] {
+ String.valueOf(HDR_TYPE_INVALID),
+ String.valueOf(HDR_TYPE_DOLBY_VISION),
+ String.valueOf(HDR_TYPE_HDR10),
+ String.valueOf(HDR_TYPE_HLG),
+ String.valueOf(HDR_TYPE_HDR10_PLUS)
+ }));
VALIDATORS.put(Global.RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO_ENABLED,
new DiscreteValueValidator(new String[]{"0", "1"}));
VALIDATORS.put(Global.Wearable.WET_MODE_ON, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 6aa08f2..574fd5a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -37,6 +37,7 @@
import android.provider.Settings;
import android.provider.settings.backup.DeviceSpecificSettings;
import android.provider.settings.backup.GlobalSettings;
+import android.provider.settings.backup.LargeScreenSettings;
import android.provider.settings.backup.SecureSettings;
import android.provider.settings.backup.SystemSettings;
import android.provider.settings.validators.GlobalSettingsValidators;
@@ -812,6 +813,12 @@
continue;
}
+ if (LargeScreenSettings.doNotRestoreIfLargeScreenSetting(key, getBaseContext())) {
+ Log.i(TAG, "Skipping restore for setting " + key + " as the target device "
+ + "is a large screen (i.e tablet or foldable in unfolded state)");
+ continue;
+ }
+
String value = null;
boolean hasValueToRestore = false;
if (cachedEntries.indexOfKey(key) >= 0) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 25b9906..7aeba95 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -238,6 +238,15 @@
dumpSetting(s, p,
Settings.Global.AUDIO_SAFE_VOLUME_STATE,
GlobalSettingsProto.AUDIO_SAFE_VOLUME_STATE);
+ dumpSetting(s, p,
+ Settings.Global.AUDIO_SAFE_CSD_CURRENT_VALUE,
+ GlobalSettingsProto.AUDIO_SAFE_CSD_CURRENT_VALUE);
+ dumpSetting(s, p,
+ Settings.Global.AUDIO_SAFE_CSD_NEXT_WARNING,
+ GlobalSettingsProto.AUDIO_SAFE_CSD_NEXT_WARNING);
+ dumpSetting(s, p,
+ Settings.Global.AUDIO_SAFE_CSD_DOSE_RECORDS,
+ GlobalSettingsProto.AUDIO_SAFE_CSD_DOSE_RECORDS);
final long autofillToken = p.start(GlobalSettingsProto.AUTOFILL);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 3782ce4..f1ea482 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -131,6 +131,9 @@
Settings.Global.ART_VERIFIER_VERIFY_DEBUGGABLE,
Settings.Global.ASSISTED_GPS_ENABLED,
Settings.Global.AUDIO_SAFE_VOLUME_STATE,
+ Settings.Global.AUDIO_SAFE_CSD_CURRENT_VALUE,
+ Settings.Global.AUDIO_SAFE_CSD_NEXT_WARNING,
+ Settings.Global.AUDIO_SAFE_CSD_DOSE_RECORDS,
Settings.Global.AUTOFILL_LOGGING_LEVEL,
Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE,
Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index cb37c07..a719d77 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -677,7 +677,7 @@
if (mScreenshotFd != null) {
writeScreenshotFile(mScreenshotFd, SCREENSHOT_CONTENT);
}
- mIDumpstateListener.onFinished();
+ mIDumpstateListener.onFinished("");
getInstrumentation().waitForIdleSync();
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 628fb38..64e1bc2 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -418,6 +418,34 @@
android:permission="com.android.systemui.permission.SELF"
android:exported="false" />
+ <activity android:name=".screenshot.AppClipsTrampolineActivity"
+ android:theme="@style/AppClipsTrampolineActivity"
+ android:label="@string/screenshot_preview_description"
+ android:permission="android.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE"
+ android:exported="true">
+ <intent-filter android:priority="1">
+ <action android:name="android.intent.action.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".screenshot.AppClipsActivity"
+ android:theme="@style/AppClipsActivity"
+ android:process=":appclips.screenshot"
+ android:label="@string/screenshot_preview_description"
+ android:permission="com.android.systemui.permission.SELF"
+ android:excludeFromRecents="true"
+ android:exported="false"
+ android:noHistory="true" />
+
+ <service android:name=".screenshot.appclips.AppClipsScreenshotHelperService"
+ android:permission="com.android.systemui.permission.SELF"
+ android:exported="false" />
+
+ <service android:name=".screenshot.appclips.AppClipsService"
+ android:permission="android.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE"
+ android:exported="true" />
+
<service android:name=".screenrecord.RecordingService"
android:foregroundServiceType="systemExempted"/>
@@ -690,6 +718,7 @@
<intent-filter>
<action android:name="android.telecom.action.SHOW_SWITCH_TO_WORK_PROFILE_FOR_CALL_DIALOG" />
<category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="tel" />
</intent-filter>
</activity>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
index 0b1a3e2..46c604b 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
@@ -25,6 +25,7 @@
"androidx.coordinatorlayout_coordinatorlayout",
"androidx.core_core",
"androidx.viewpager_viewpager",
+ "SettingsLib",
],
uses_libs: [
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
index 26748a9..77412d9 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -16,11 +16,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.android.systemui.accessibility.accessibilitymenu">
- <application>
+
+ <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/>
+
+ <application android:supportsRtl="true">
<service
android:name="com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService"
android:exported="false"
- android:label="Accessibility Menu (System)"
+ android:label="@string/accessibility_menu_service_name"
+ android:icon="@drawable/ic_logo_app"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-sw600dp-night/a11ymenu_intro.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-sw600dp-night/a11ymenu_intro.xml
new file mode 100644
index 0000000..7a4fbb2
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-sw600dp-night/a11ymenu_intro.xml
@@ -0,0 +1,276 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="412dp"
+ android:height="300dp"
+ android:viewportWidth="412"
+ android:viewportHeight="300">
+ <group>
+ <clip-path
+ android:pathData="M62.23,50.19L349.77,50.19A16,16 0,0 1,365.77 66.19L365.77,236.14A16,16 0,0 1,349.77 252.14L62.23,252.14A16,16 0,0 1,46.23 236.14L46.23,66.19A16,16 0,0 1,62.23 50.19z"/>
+ <path
+ android:pathData="M62.23,50.19L349.77,50.19A16,16 0,0 1,365.77 66.19L365.77,236.14A16,16 0,0 1,349.77 252.14L62.23,252.14A16,16 0,0 1,46.23 236.14L46.23,66.19A16,16 0,0 1,62.23 50.19z"
+ android:fillColor="#3B4043"/>
+ <path
+ android:pathData="M46.23,52.14h160v200h-160z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M86.21,240.04L86.91,239.34L84.21,236.64H90.11V235.64H84.21L86.91,232.94L86.21,232.24L82.31,236.14L86.21,240.04Z"
+ android:fillColor="#3B4043"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M125.71,220.14V252.14H127.71V220.14H125.71Z"
+ android:fillColor="#3B4043"/>
+ <path
+ android:pathData="M166.21,232.24L165.51,232.94L168.21,235.64H162.31V236.64H168.21L165.51,239.34L166.21,240.04L170.11,236.14L166.21,232.24Z"
+ android:fillColor="#7F868C"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M46.71,221.14H206.71V219.14H46.71V221.14Z"
+ android:fillColor="#3B4043"/>
+ <path
+ android:pathData="M78.71,120.5L78.71,120.5A16,16 0,0 1,94.71 136.5L94.71,136.5A16,16 0,0 1,78.71 152.5L78.71,152.5A16,16 0,0 1,62.71 136.5L62.71,136.5A16,16 0,0 1,78.71 120.5z"
+ android:fillColor="#327969"/>
+ <group>
+ <clip-path
+ android:pathData="M73.36,138.95V134.15H76.56L80.56,130.15V142.85L76.56,138.85H73.36V138.95ZM82.06,133.35C83.26,133.95 84.06,135.15 84.06,136.55C84.06,137.95 83.26,139.15 82.06,139.75V133.35Z"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M88.36,127.05H69.36V146.05H88.36V127.05Z"
+ android:fillColor="#000000"/>
+ </group>
+ <path
+ android:pathData="M78.71,172.5L78.71,172.5A16,16 0,0 1,94.71 188.5L94.71,188.5A16,16 0,0 1,78.71 204.5L78.71,204.5A16,16 0,0 1,62.71 188.5L62.71,188.5A16,16 0,0 1,78.71 172.5z"
+ android:fillColor="#DE9834"/>
+ <path
+ android:pathData="M87.66,188.5L85.06,191.1V194.8H81.36L78.76,197.4L76.16,194.8H72.36V191.1L69.76,188.5L72.36,185.9V182.2H76.06L78.66,179.6L81.26,182.2H84.96V185.9L87.66,188.5ZM73.96,188.5C73.96,191.1 76.06,193.3 78.76,193.3C81.36,193.3 83.56,191.2 83.56,188.5C83.56,185.8 81.46,183.7 78.76,183.7C76.06,183.7 73.96,185.8 73.96,188.5Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M80.66,187.9H76.66V189.4H80.66V187.9Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M126.71,120.5L126.71,120.5A16,16 0,0 1,142.71 136.5L142.71,136.5A16,16 0,0 1,126.71 152.5L126.71,152.5A16,16 0,0 1,110.71 136.5L110.71,136.5A16,16 0,0 1,126.71 120.5z"
+ android:fillColor="#327969"/>
+ <path
+ android:pathData="M128.31,131.1V129.5C131.51,130.2 133.91,133.1 133.91,136.5C133.91,139.9 131.51,142.7 128.31,143.5V141.9C130.61,141.2 132.31,139.1 132.31,136.6C132.31,134.1 130.61,131.8 128.31,131.1ZM130.31,136.4C130.31,135 129.51,133.8 128.31,133.2V139.6C129.41,139 130.31,137.8 130.31,136.4Z"
+ android:fillColor="#8B3737"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M126.72,142.79V130.13L122.71,134.08H119.5V138.78H122.71L126.72,142.79Z"
+ android:fillColor="#8B3737"/>
+ <group>
+ <clip-path
+ android:pathData="M126.72,142.79V130.13L122.71,134.08H119.5V138.78H122.71L126.72,142.79Z"/>
+ <clip-path
+ android:pathData="M128.31,131.1V129.5C131.51,130.2 133.91,133.1 133.91,136.5C133.91,139.9 131.51,142.7 128.31,143.5V141.9C130.61,141.2 132.31,139.1 132.31,136.6C132.31,134.1 130.61,131.8 128.31,131.1ZM130.31,136.4C130.31,135 129.51,133.8 128.31,133.2V139.6C129.41,139 130.31,137.8 130.31,136.4Z"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M128.31,131.1H78.31V168.15L113.75,178.94L128.31,131.1ZM128.31,129.5L138.99,80.66L78.31,67.38V129.5H128.31ZM128.31,143.5H78.31V207.54L140.44,192.01L128.31,143.5ZM128.31,141.9L113.75,94.07L78.31,104.86V141.9H128.31ZM128.31,133.2L150.67,88.48L78.31,52.3V133.2H128.31ZM128.31,139.6H78.31V223.83L152.25,183.5L128.31,139.6ZM126.72,130.13H176.73V10.79L91.66,94.5L126.72,130.13ZM126.72,142.79L91.37,178.15L176.73,263.5V142.79H126.72ZM122.71,138.78L158.07,103.43L143.43,88.78H122.71V138.78ZM119.5,138.78H69.5V188.78H119.5V138.78ZM119.5,134.08V84.08H69.5V134.08H119.5ZM122.71,134.08V184.08H143.19L157.78,169.72L122.71,134.08ZM178.31,131.1V129.5H78.31V131.1H178.31ZM117.63,178.35C98.03,174.06 83.91,156.76 83.91,136.5H183.91C183.91,109.44 164.99,86.34 138.99,80.66L117.63,178.35ZM83.91,136.5C83.91,115.15 98.93,99.31 116.18,94.99L140.44,192.01C164.09,186.1 183.91,164.66 183.91,136.5H83.91ZM178.31,143.5V141.9H78.31V143.5H178.31ZM142.87,189.74C164.72,183.09 182.31,162.64 182.31,136.6H82.31C82.31,115.57 96.5,99.32 113.75,94.07L142.87,189.74ZM182.31,136.6C182.31,112.64 166.74,90.53 142.87,83.27L113.75,178.94C94.48,173.07 82.31,155.56 82.31,136.6H182.31ZM180.31,136.4C180.31,115.04 167.88,97.08 150.67,88.48L105.95,177.92C91.14,170.52 80.31,154.96 80.31,136.4H180.31ZM78.31,133.2V139.6H178.31V133.2H78.31ZM152.25,183.5C166.09,175.95 180.31,159.6 180.31,136.4H80.31C80.31,116 92.73,102.06 104.37,95.71L152.25,183.5ZM76.72,130.13V142.79H176.73V130.13H76.72ZM162.08,107.44L158.07,103.43L87.36,174.14L91.37,178.15L162.08,107.44ZM122.71,88.78H119.5V188.78H122.71V88.78ZM169.5,138.78V134.08H69.5V138.78H169.5ZM119.5,184.08H122.71V84.08H119.5V184.08ZM157.78,169.72L161.79,165.77L91.66,94.5L87.65,98.44L157.78,169.72Z"
+ android:fillColor="#000000"/>
+ </group>
+ <path
+ android:pathData="M78.71,68.5L78.71,68.5A16,16 0,0 1,94.71 84.5L94.71,84.5A16,16 0,0 1,78.71 100.5L78.71,100.5A16,16 0,0 1,62.71 84.5L62.71,84.5A16,16 0,0 1,78.71 68.5z"
+ android:fillColor="#3B4043"/>
+ <path
+ android:pathData="M86.26,82.15C86.92,82.15 87.46,81.61 87.46,80.95C87.46,80.29 86.92,79.75 86.26,79.75C85.6,79.75 85.06,80.29 85.06,80.95C85.06,81.61 85.6,82.15 86.26,82.15Z"
+ android:fillColor="#34A853"/>
+ <path
+ android:pathData="M82.66,86.15C83.99,86.15 85.06,85.08 85.06,83.75C85.06,82.43 83.99,81.35 82.66,81.35C81.34,81.35 80.26,82.43 80.26,83.75C80.26,85.08 81.34,86.15 82.66,86.15Z"
+ android:fillColor="#EA4335"/>
+ <path
+ android:pathData="M82.66,92.45C84.21,92.45 85.46,91.2 85.46,89.65C85.46,88.11 84.21,86.85 82.66,86.85C81.11,86.85 79.86,88.11 79.86,89.65C79.86,91.2 81.11,92.45 82.66,92.45Z"
+ android:fillColor="#FBBC04"/>
+ <path
+ android:pathData="M74.76,86.15C77.41,86.15 79.56,84 79.56,81.35C79.56,78.7 77.41,76.55 74.76,76.55C72.11,76.55 69.96,78.7 69.96,81.35C69.96,84 72.11,86.15 74.76,86.15Z"
+ android:fillColor="#4285F4"/>
+ <path
+ android:pathData="M174.71,120.5L174.71,120.5A16,16 0,0 1,190.71 136.5L190.71,136.5A16,16 0,0 1,174.71 152.5L174.71,152.5A16,16 0,0 1,158.71 136.5L158.71,136.5A16,16 0,0 1,174.71 120.5z"
+ android:fillColor="#9F3EBF"/>
+ <path
+ android:pathData="M178.53,130.5H171.03V142.2H178.53V130.5Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M170.23,132.8H167.53V140H170.23V132.8Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M182.23,132.8H179.53V140H182.23V132.8Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M126.71,172.5L126.71,172.5A16,16 0,0 1,142.71 188.5L142.71,188.5A16,16 0,0 1,126.71 204.5L126.71,204.5A16,16 0,0 1,110.71 188.5L110.71,188.5A16,16 0,0 1,126.71 172.5z"
+ android:fillColor="#DE9834"/>
+ <path
+ android:pathData="M135.71,188.5L133.11,191.1V194.8H129.31L126.71,197.4L124.11,194.8H120.31V191.1L117.71,188.5L120.31,185.9V182.2H124.01L126.61,179.6L129.21,182.2H132.91V185.9L135.71,188.5ZM121.91,188.5C121.91,191.1 124.01,193.3 126.71,193.3C129.31,193.3 131.51,191.2 131.51,188.5C131.51,185.8 129.41,183.7 126.71,183.7C124.11,183.7 121.91,185.8 121.91,188.5Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M127.21,187.9V186.4H126.21V187.9H124.71V188.9H126.21V190.4H127.21V188.9H128.71V187.9H127.21Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M126.71,68.5L126.71,68.5A16,16 0,0 1,142.71 84.5L142.71,84.5A16,16 0,0 1,126.71 100.5L126.71,100.5A16,16 0,0 1,110.71 84.5L110.71,84.5A16,16 0,0 1,126.71 68.5z"
+ android:fillColor="#521BBF"/>
+ <path
+ android:pathData="M128.36,78.51C128.36,79.31 127.66,80.01 126.76,80.01C125.86,80.01 125.16,79.31 125.16,78.51C125.16,77.71 125.86,77.01 126.76,77.01C127.66,76.91 128.36,77.61 128.36,78.51ZM126.76,80.71C128.96,80.71 131.46,80.51 133.46,79.91L133.86,81.41C132.36,81.81 130.66,82.01 129.06,82.21V92.01H127.46V87.51H125.86V92.01H124.36V82.21C122.76,82.11 121.06,81.81 119.56,81.41L119.96,79.91C122.06,80.51 124.46,80.71 126.76,80.71Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M174.71,172.5L174.71,172.5A16,16 0,0 1,190.71 188.5L190.71,188.5A16,16 0,0 1,174.71 204.5L174.71,204.5A16,16 0,0 1,158.71 188.5L158.71,188.5A16,16 0,0 1,174.71 172.5z"
+ android:fillColor="#438947"/>
+ <path
+ android:pathData="M179.11,185.95H178.41V184.45C178.41,182.45 176.81,180.75 174.71,180.75C172.61,180.75 171.01,182.35 171.01,184.45V185.95H170.31C169.51,185.95 168.81,186.65 168.81,187.45V194.75C168.81,195.55 169.51,196.25 170.31,196.25H179.11C179.91,196.25 180.61,195.55 180.61,194.75V187.45C180.61,186.65 179.91,185.95 179.11,185.95ZM174.71,192.55C173.91,192.55 173.21,191.85 173.21,191.05C173.21,190.25 173.91,189.55 174.71,189.55C175.51,189.55 176.21,190.25 176.21,191.05C176.21,191.95 175.51,192.55 174.71,192.55ZM172.41,185.95H176.91V184.45C176.91,183.15 175.91,182.15 174.61,182.15C173.31,182.15 172.31,183.15 172.31,184.45V185.95H172.41Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M174.71,68.5L174.71,68.5A16,16 0,0 1,190.71 84.5L190.71,84.5A16,16 0,0 1,174.71 100.5L174.71,100.5A16,16 0,0 1,158.71 84.5L158.71,84.5A16,16 0,0 1,174.71 68.5z"
+ android:fillColor="#80868B"/>
+ <path
+ android:pathData="M173.91,77.35H175.51V85.25H173.91V77.35ZM178.21,80.25L179.31,79.15C180.81,80.45 181.81,82.35 181.81,84.55C181.81,88.45 178.61,91.65 174.71,91.65C170.81,91.65 167.61,88.45 167.61,84.55C167.61,82.35 168.61,80.45 170.11,79.15L171.21,80.25C170.01,81.25 169.21,82.85 169.21,84.55C169.21,87.65 171.71,90.15 174.81,90.15C177.91,90.15 180.41,87.65 180.41,84.55C180.31,82.75 179.41,81.25 178.21,80.25Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ </group>
+ <path
+ android:pathData="M62.23,51.69L349.77,51.69A14.5,14.5 0,0 1,364.27 66.19L364.27,236.14A14.5,14.5 0,0 1,349.77 250.64L62.23,250.64A14.5,14.5 0,0 1,47.73 236.14L47.73,66.19A14.5,14.5 0,0 1,62.23 51.69z"
+ android:strokeWidth="3"
+ android:fillColor="#00000000"
+ android:strokeColor="#7F868C"/>
+ <path
+ android:pathData="M311.45,50.35C311.45,48.98 312.56,47.87 313.92,47.87L322.84,47.87C324.2,47.87 325.32,48.98 325.32,50.35L319.37,51.34L311.45,50.35Z"
+ android:fillColor="#7F868C"/>
+ <path
+ android:pathData="M263.59,50.35C263.59,48.98 264.7,47.87 266.06,47.87L287.85,47.87C289.22,47.87 290.33,48.98 290.33,50.35L277.45,51.34L263.59,50.35Z"
+ android:fillColor="#7F868C"/>
+ <group>
+ <clip-path
+ android:pathData="M62,50.32L349.54,50.32A16,16 0,0 1,365.54 66.32L365.54,236.27A16,16 0,0 1,349.54 252.27L62,252.27A16,16 0,0 1,46 236.27L46,66.32A16,16 0,0 1,62 50.32z"/>
+ <path
+ android:pathData="M62,50.32L349.54,50.32A16,16 0,0 1,365.54 66.32L365.54,236.27A16,16 0,0 1,349.54 252.27L62,252.27A16,16 0,0 1,46 236.27L46,66.32A16,16 0,0 1,62 50.32z"
+ android:fillColor="#3B4043"/>
+ <path
+ android:pathData="M46,52.27h160v200h-160z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M85.98,240.17L86.68,239.47L83.98,236.77H89.88V235.77H83.98L86.68,233.07L85.98,232.37L82.08,236.27L85.98,240.17Z"
+ android:fillColor="#3B4043"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M125.48,220.27V252.27H127.48V220.27H125.48Z"
+ android:fillColor="#3B4043"/>
+ <path
+ android:pathData="M165.98,232.37L165.28,233.07L167.98,235.77H162.08V236.77H167.98L165.28,239.47L165.98,240.17L169.88,236.27L165.98,232.37Z"
+ android:fillColor="#7F868C"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M46.48,221.27H206.48V219.27H46.48V221.27Z"
+ android:fillColor="#3B4043"/>
+ <path
+ android:pathData="M78.48,120.64L78.48,120.64A16,16 0,0 1,94.48 136.64L94.48,136.64A16,16 0,0 1,78.48 152.64L78.48,152.64A16,16 0,0 1,62.48 136.64L62.48,136.64A16,16 0,0 1,78.48 120.64z"
+ android:fillColor="#7AE2D4"/>
+ <group>
+ <clip-path
+ android:pathData="M73.13,139.09V134.29H76.33L80.33,130.29V142.99L76.33,138.99H73.13V139.09ZM81.83,133.49C83.03,134.09 83.83,135.29 83.83,136.69C83.83,138.09 83.03,139.29 81.83,139.89V133.49Z"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M88.13,127.19H69.13V146.19H88.13V127.19Z"
+ android:fillColor="#000000"/>
+ </group>
+ <path
+ android:pathData="M78.48,172.64L78.48,172.64A16,16 0,0 1,94.48 188.64L94.48,188.64A16,16 0,0 1,78.48 204.64L78.48,204.64A16,16 0,0 1,62.48 188.64L62.48,188.64A16,16 0,0 1,78.48 172.64z"
+ android:fillColor="#FDD663"/>
+ <path
+ android:pathData="M87.43,188.64L84.83,191.24V194.94H81.13L78.53,197.54L75.93,194.94H72.13V191.24L69.53,188.64L72.13,186.04V182.34H75.83L78.43,179.74L81.03,182.34H84.73V186.04L87.43,188.64ZM73.73,188.64C73.73,191.24 75.83,193.44 78.53,193.44C81.13,193.44 83.33,191.34 83.33,188.64C83.33,185.94 81.23,183.84 78.53,183.84C75.83,183.84 73.73,185.94 73.73,188.64Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M80.43,188.04H76.43V189.54H80.43V188.04Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M126.48,120.64L126.48,120.64A16,16 0,0 1,142.48 136.64L142.48,136.64A16,16 0,0 1,126.48 152.64L126.48,152.64A16,16 0,0 1,110.48 136.64L110.48,136.64A16,16 0,0 1,126.48 120.64z"
+ android:fillColor="#7AE2D4"/>
+ <path
+ android:pathData="M128.08,131.24V129.64C131.28,130.34 133.68,133.24 133.68,136.64C133.68,140.04 131.28,142.84 128.08,143.64V142.04C130.38,141.34 132.08,139.24 132.08,136.74C132.08,134.24 130.38,131.94 128.08,131.24ZM130.08,136.54C130.08,135.14 129.28,133.94 128.08,133.34V139.74C129.18,139.14 130.08,137.94 130.08,136.54Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M126.49,142.93V130.27L122.49,134.21H119.27V138.92H122.49L126.49,142.93Z"
+ android:fillColor="#000000"/>
+ <group>
+ <clip-path
+ android:pathData="M126.49,142.93V130.27L122.49,134.21H119.27V138.92H122.49L126.49,142.93Z"/>
+ <clip-path
+ android:pathData="M128.08,131.24V129.64C131.28,130.34 133.68,133.24 133.68,136.64C133.68,140.04 131.28,142.84 128.08,143.64V142.04C130.38,141.34 132.08,139.24 132.08,136.74C132.08,134.24 130.38,131.94 128.08,131.24ZM130.08,136.54C130.08,135.14 129.28,133.94 128.08,133.34V139.74C129.18,139.14 130.08,137.94 130.08,136.54Z"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M128.08,131.24H78.08V168.28L113.52,179.07L128.08,131.24ZM128.08,129.64L138.76,80.79L78.08,67.52V129.64H128.08ZM128.08,143.64H78.08V207.68L140.21,192.14L128.08,143.64ZM128.08,142.04L113.52,94.2L78.08,104.99V142.04H128.08ZM128.08,133.34L150.44,88.62L78.08,52.44V133.34H128.08ZM128.08,139.74H78.08V223.96L152.02,183.63L128.08,139.74ZM126.49,130.27H176.49V10.92L91.43,94.63L126.49,130.27ZM126.49,142.93L91.14,178.28L176.49,263.64V142.93H126.49ZM122.49,138.92L157.84,103.56L143.2,88.92H122.49V138.92ZM119.27,138.92H69.27V188.92H119.27V138.92ZM119.27,134.21V84.22H69.27V134.21H119.27ZM122.49,134.21V184.21H142.96L157.55,169.85L122.49,134.21ZM178.08,131.24V129.64H78.08V131.24H178.08ZM117.39,178.48C97.8,174.2 83.68,156.9 83.68,136.64H183.68C183.68,109.57 164.76,86.48 138.76,80.79L117.39,178.48ZM83.68,136.64C83.68,115.28 98.7,99.44 115.95,95.13L140.21,192.14C163.86,186.23 183.68,164.79 183.68,136.64H83.68ZM178.08,143.64V142.04H78.08V143.64H178.08ZM142.64,189.87C164.49,183.22 182.08,162.77 182.08,136.74H82.08C82.08,115.7 96.27,99.45 113.52,94.2L142.64,189.87ZM182.08,136.74C182.08,112.78 166.51,90.67 142.64,83.4L113.52,179.07C94.25,173.2 82.08,155.69 82.08,136.74H182.08ZM180.08,136.54C180.08,115.18 167.65,97.22 150.44,88.62L105.72,178.06C90.91,170.65 80.08,155.1 80.08,136.54H180.08ZM78.08,133.34V139.74H178.08V133.34H78.08ZM152.02,183.63C165.86,176.08 180.08,159.73 180.08,136.54H80.08C80.08,116.14 92.5,102.19 104.14,95.84L152.02,183.63ZM76.49,130.27V142.93H176.49V130.27H76.49ZM161.85,107.57L157.84,103.56L87.13,174.27L91.14,178.28L161.85,107.57ZM122.49,88.92H119.27V188.92H122.49V88.92ZM169.27,138.92V134.21H69.27V138.92H169.27ZM119.27,184.21H122.49V84.22H119.27V184.21ZM157.55,169.85L161.56,165.91L91.43,94.63L87.42,98.58L157.55,169.85Z"
+ android:fillColor="#000000"/>
+ </group>
+ <path
+ android:pathData="M78.48,68.64L78.48,68.64A16,16 0,0 1,94.48 84.64L94.48,84.64A16,16 0,0 1,78.48 100.64L78.48,100.64A16,16 0,0 1,62.48 84.64L62.48,84.64A16,16 0,0 1,78.48 68.64z"
+ android:fillColor="#3B4043"/>
+ <path
+ android:pathData="M86.03,82.29C86.69,82.29 87.23,81.75 87.23,81.09C87.23,80.42 86.69,79.89 86.03,79.89C85.37,79.89 84.83,80.42 84.83,81.09C84.83,81.75 85.37,82.29 86.03,82.29Z"
+ android:fillColor="#34A853"/>
+ <path
+ android:pathData="M82.43,86.29C83.76,86.29 84.83,85.21 84.83,83.89C84.83,82.56 83.76,81.49 82.43,81.49C81.11,81.49 80.03,82.56 80.03,83.89C80.03,85.21 81.11,86.29 82.43,86.29Z"
+ android:fillColor="#EA4335"/>
+ <path
+ android:pathData="M82.43,92.59C83.98,92.59 85.23,91.33 85.23,89.79C85.23,88.24 83.98,86.99 82.43,86.99C80.88,86.99 79.63,88.24 79.63,89.79C79.63,91.33 80.88,92.59 82.43,92.59Z"
+ android:fillColor="#FBBC04"/>
+ <path
+ android:pathData="M74.53,86.29C77.18,86.29 79.33,84.14 79.33,81.49C79.33,78.84 77.18,76.69 74.53,76.69C71.88,76.69 69.73,78.84 69.73,81.49C69.73,84.14 71.88,86.29 74.53,86.29Z"
+ android:fillColor="#4285F4"/>
+ <path
+ android:pathData="M174.48,120.64L174.48,120.64A16,16 0,0 1,190.48 136.64L190.48,136.64A16,16 0,0 1,174.48 152.64L174.48,152.64A16,16 0,0 1,158.48 136.64L158.48,136.64A16,16 0,0 1,174.48 120.64z"
+ android:fillColor="#EFA5DE"/>
+ <path
+ android:pathData="M178.3,130.64H170.8V142.34H178.3V130.64Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M170,132.94H167.3V140.14H170V132.94Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M182,132.94H179.3V140.14H182V132.94Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M126.48,172.64L126.48,172.64A16,16 0,0 1,142.48 188.64L142.48,188.64A16,16 0,0 1,126.48 204.64L126.48,204.64A16,16 0,0 1,110.48 188.64L110.48,188.64A16,16 0,0 1,126.48 172.64z"
+ android:fillColor="#FDD663"/>
+ <path
+ android:pathData="M135.48,188.64L132.88,191.24V194.94H129.08L126.48,197.54L123.88,194.94H120.08V191.24L117.48,188.64L120.08,186.04V182.34H123.78L126.38,179.74L128.98,182.34H132.68V186.04L135.48,188.64ZM121.68,188.64C121.68,191.24 123.78,193.44 126.48,193.44C129.08,193.44 131.28,191.34 131.28,188.64C131.28,185.94 129.18,183.84 126.48,183.84C123.88,183.84 121.68,185.94 121.68,188.64Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M126.98,188.04V186.54H125.98V188.04H124.48V189.04H125.98V190.54H126.98V189.04H128.48V188.04H126.98Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M126.48,68.64L126.48,68.64A16,16 0,0 1,142.48 84.64L142.48,84.64A16,16 0,0 1,126.48 100.64L126.48,100.64A16,16 0,0 1,110.48 84.64L110.48,84.64A16,16 0,0 1,126.48 68.64z"
+ android:fillColor="#D9AFFD"/>
+ <path
+ android:pathData="M128.13,78.64C128.13,79.44 127.43,80.14 126.53,80.14C125.63,80.14 124.93,79.44 124.93,78.64C124.93,77.84 125.63,77.14 126.53,77.14C127.43,77.04 128.13,77.74 128.13,78.64ZM126.53,80.84C128.73,80.84 131.23,80.64 133.23,80.04L133.63,81.54C132.13,81.94 130.43,82.14 128.83,82.34V92.14H127.23V87.64H125.63V92.14H124.13V82.34C122.53,82.24 120.83,81.94 119.33,81.54L119.73,80.04C121.83,80.64 124.23,80.84 126.53,80.84Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M174.48,172.64L174.48,172.64A16,16 0,0 1,190.48 188.64L190.48,188.64A16,16 0,0 1,174.48 204.64L174.48,204.64A16,16 0,0 1,158.48 188.64L158.48,188.64A16,16 0,0 1,174.48 172.64z"
+ android:fillColor="#84E39F"/>
+ <path
+ android:pathData="M178.88,186.09H178.18V184.59C178.18,182.59 176.58,180.89 174.48,180.89C172.38,180.89 170.78,182.49 170.78,184.59V186.09H170.08C169.28,186.09 168.58,186.79 168.58,187.59V194.89C168.58,195.69 169.28,196.39 170.08,196.39H178.88C179.68,196.39 180.38,195.69 180.38,194.89V187.59C180.38,186.79 179.68,186.09 178.88,186.09ZM174.48,192.69C173.68,192.69 172.98,191.99 172.98,191.19C172.98,190.39 173.68,189.69 174.48,189.69C175.28,189.69 175.98,190.39 175.98,191.19C175.98,192.09 175.28,192.69 174.48,192.69ZM172.18,186.09H176.68V184.59C176.68,183.29 175.68,182.29 174.38,182.29C173.08,182.29 172.08,183.29 172.08,184.59V186.09H172.18Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M174.48,68.64L174.48,68.64A16,16 0,0 1,190.48 84.64L190.48,84.64A16,16 0,0 1,174.48 100.64L174.48,100.64A16,16 0,0 1,158.48 84.64L158.48,84.64A16,16 0,0 1,174.48 68.64z"
+ android:fillColor="#DBDCE0"/>
+ <path
+ android:pathData="M173.68,77.49H175.28V85.39H173.68V77.49ZM177.98,80.39L179.08,79.29C180.58,80.59 181.58,82.49 181.58,84.69C181.58,88.59 178.38,91.79 174.48,91.79C170.58,91.79 167.38,88.59 167.38,84.69C167.38,82.49 168.38,80.59 169.88,79.29L170.98,80.39C169.78,81.39 168.98,82.99 168.98,84.69C168.98,87.79 171.48,90.29 174.58,90.29C177.68,90.29 180.18,87.79 180.18,84.69C180.08,82.89 179.18,81.39 177.98,80.39Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ </group>
+ <path
+ android:pathData="M62,51.82L349.54,51.82A14.5,14.5 0,0 1,364.04 66.32L364.04,236.27A14.5,14.5 0,0 1,349.54 250.77L62,250.77A14.5,14.5 0,0 1,47.5 236.27L47.5,66.32A14.5,14.5 0,0 1,62 51.82z"
+ android:strokeWidth="3"
+ android:fillColor="#00000000"
+ android:strokeColor="#7F868C"/>
+ <path
+ android:pathData="M311.22,50.49C311.22,49.11 312.33,48 313.7,48L322.61,48C323.98,48 325.08,49.11 325.08,50.49L319.14,51.48L311.22,50.49Z"
+ android:fillColor="#7F868C"/>
+ <path
+ android:pathData="M263.36,50.49C263.36,49.11 264.47,48 265.83,48L287.62,48C288.99,48 290.1,49.11 290.1,50.49L277.22,51.48L263.36,50.49Z"
+ android:fillColor="#7F868C"/>
+</vector>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-sw600dp/a11ymenu_intro.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-sw600dp/a11ymenu_intro.xml
new file mode 100644
index 0000000..f0fbc48
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable-sw600dp/a11ymenu_intro.xml
@@ -0,0 +1,141 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="412dp"
+ android:height="300dp"
+ android:viewportWidth="412"
+ android:viewportHeight="300">
+ <group>
+ <clip-path
+ android:pathData="M62.23,50.19L349.77,50.19A16,16 0,0 1,365.77 66.19L365.77,236.14A16,16 0,0 1,349.77 252.14L62.23,252.14A16,16 0,0 1,46.23 236.14L46.23,66.19A16,16 0,0 1,62.23 50.19z"/>
+ <path
+ android:pathData="M62.23,50.19L349.77,50.19A16,16 0,0 1,365.77 66.19L365.77,236.14A16,16 0,0 1,349.77 252.14L62.23,252.14A16,16 0,0 1,46.23 236.14L46.23,66.19A16,16 0,0 1,62.23 50.19z"
+ android:fillColor="#E8EAED"/>
+ <path
+ android:pathData="M46.23,52.14h160v200h-160z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M86.21,240.04L86.91,239.34L84.21,236.64H90.11V235.64H84.21L86.91,232.94L86.21,232.24L82.31,236.14L86.21,240.04Z"
+ android:fillColor="#E8EAED"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M125.71,220.14V252.14H127.71V220.14H125.71Z"
+ android:fillColor="#E8EAED"/>
+ <path
+ android:pathData="M166.21,232.24L165.51,232.94L168.21,235.64H162.31V236.64H168.21L165.51,239.34L166.21,240.04L170.11,236.14L166.21,232.24Z"
+ android:fillColor="#7F868C"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M46.71,221.14H206.71V219.14H46.71V221.14Z"
+ android:fillColor="#E8EAED"/>
+ <path
+ android:pathData="M78.71,120.5L78.71,120.5A16,16 0,0 1,94.71 136.5L94.71,136.5A16,16 0,0 1,78.71 152.5L78.71,152.5A16,16 0,0 1,62.71 136.5L62.71,136.5A16,16 0,0 1,78.71 120.5z"
+ android:fillColor="#327969"/>
+ <group>
+ <clip-path
+ android:pathData="M73.36,138.95V134.15H76.56L80.56,130.15V142.85L76.56,138.85H73.36V138.95ZM82.06,133.35C83.26,133.95 84.06,135.15 84.06,136.55C84.06,137.95 83.26,139.15 82.06,139.75V133.35Z"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M88.36,127.05H69.36V146.05H88.36V127.05Z"
+ android:fillColor="#ffffff"/>
+ </group>
+ <path
+ android:pathData="M78.71,172.5L78.71,172.5A16,16 0,0 1,94.71 188.5L94.71,188.5A16,16 0,0 1,78.71 204.5L78.71,204.5A16,16 0,0 1,62.71 188.5L62.71,188.5A16,16 0,0 1,78.71 172.5z"
+ android:fillColor="#DE9834"/>
+ <path
+ android:pathData="M87.66,188.5L85.06,191.1V194.8H81.36L78.76,197.4L76.16,194.8H72.36V191.1L69.76,188.5L72.36,185.9V182.2H76.06L78.66,179.6L81.26,182.2H84.96V185.9L87.66,188.5ZM73.96,188.5C73.96,191.1 76.06,193.3 78.76,193.3C81.36,193.3 83.56,191.2 83.56,188.5C83.56,185.8 81.46,183.7 78.76,183.7C76.06,183.7 73.96,185.8 73.96,188.5Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M80.66,187.9H76.66V189.4H80.66V187.9Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M126.71,120.5L126.71,120.5A16,16 0,0 1,142.71 136.5L142.71,136.5A16,16 0,0 1,126.71 152.5L126.71,152.5A16,16 0,0 1,110.71 136.5L110.71,136.5A16,16 0,0 1,126.71 120.5z"
+ android:fillColor="#327969"/>
+ <path
+ android:pathData="M128.31,131.1V129.5C131.51,130.2 133.91,133.1 133.91,136.5C133.91,139.9 131.51,142.7 128.31,143.5V141.9C130.61,141.2 132.31,139.1 132.31,136.6C132.31,134.1 130.61,131.8 128.31,131.1ZM130.31,136.4C130.31,135 129.51,133.8 128.31,133.2V139.6C129.41,139 130.31,137.8 130.31,136.4Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M126.72,142.79V130.13L122.71,134.08H119.5V138.78H122.71L126.72,142.79Z"
+ android:fillColor="#ffffff"/>
+ <group>
+ <clip-path
+ android:pathData="M126.72,142.79V130.13L122.71,134.08H119.5V138.78H122.71L126.72,142.79Z"/>
+ <clip-path
+ android:pathData="M128.31,131.1V129.5C131.51,130.2 133.91,133.1 133.91,136.5C133.91,139.9 131.51,142.7 128.31,143.5V141.9C130.61,141.2 132.31,139.1 132.31,136.6C132.31,134.1 130.61,131.8 128.31,131.1ZM130.31,136.4C130.31,135 129.51,133.8 128.31,133.2V139.6C129.41,139 130.31,137.8 130.31,136.4Z"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M128.31,131.1H78.31V168.15L113.75,178.94L128.31,131.1ZM128.31,129.5L138.99,80.66L78.31,67.38V129.5H128.31ZM128.31,143.5H78.31V207.54L140.44,192.01L128.31,143.5ZM128.31,141.9L113.75,94.07L78.31,104.86V141.9H128.31ZM128.31,133.2L150.67,88.48L78.31,52.3V133.2H128.31ZM128.31,139.6H78.31V223.83L152.25,183.5L128.31,139.6ZM126.72,130.13H176.73V10.79L91.66,94.5L126.72,130.13ZM126.72,142.79L91.37,178.15L176.73,263.5V142.79H126.72ZM122.71,138.78L158.07,103.43L143.43,88.78H122.71V138.78ZM119.5,138.78H69.5V188.78H119.5V138.78ZM119.5,134.08V84.08H69.5V134.08H119.5ZM122.71,134.08V184.08H143.19L157.78,169.72L122.71,134.08ZM178.31,131.1V129.5H78.31V131.1H178.31ZM117.63,178.35C98.03,174.06 83.91,156.76 83.91,136.5H183.91C183.91,109.44 164.99,86.34 138.99,80.66L117.63,178.35ZM83.91,136.5C83.91,115.15 98.93,99.31 116.18,94.99L140.44,192.01C164.09,186.1 183.91,164.66 183.91,136.5H83.91ZM178.31,143.5V141.9H78.31V143.5H178.31ZM142.87,189.74C164.72,183.09 182.31,162.64 182.31,136.6H82.31C82.31,115.57 96.5,99.32 113.75,94.07L142.87,189.74ZM182.31,136.6C182.31,112.64 166.74,90.53 142.87,83.27L113.75,178.94C94.48,173.07 82.31,155.56 82.31,136.6H182.31ZM180.31,136.4C180.31,115.04 167.88,97.08 150.67,88.48L105.95,177.92C91.14,170.52 80.31,154.96 80.31,136.4H180.31ZM78.31,133.2V139.6H178.31V133.2H78.31ZM152.25,183.5C166.09,175.95 180.31,159.6 180.31,136.4H80.31C80.31,116 92.73,102.06 104.37,95.71L152.25,183.5ZM76.72,130.13V142.79H176.73V130.13H76.72ZM162.08,107.44L158.07,103.43L87.36,174.14L91.37,178.15L162.08,107.44ZM122.71,88.78H119.5V188.78H122.71V88.78ZM169.5,138.78V134.08H69.5V138.78H169.5ZM119.5,184.08H122.71V84.08H119.5V184.08ZM157.78,169.72L161.79,165.77L91.66,94.5L87.65,98.44L157.78,169.72Z"
+ android:fillColor="#ffffff"/>
+ </group>
+ <path
+ android:pathData="M78.71,68.5L78.71,68.5A16,16 0,0 1,94.71 84.5L94.71,84.5A16,16 0,0 1,78.71 100.5L78.71,100.5A16,16 0,0 1,62.71 84.5L62.71,84.5A16,16 0,0 1,78.71 68.5z"
+ android:fillColor="#E8EAED"/>
+ <path
+ android:pathData="M86.26,82.15C86.92,82.15 87.46,81.61 87.46,80.95C87.46,80.29 86.92,79.75 86.26,79.75C85.6,79.75 85.06,80.29 85.06,80.95C85.06,81.61 85.6,82.15 86.26,82.15Z"
+ android:fillColor="#34A853"/>
+ <path
+ android:pathData="M82.66,86.15C83.99,86.15 85.06,85.08 85.06,83.75C85.06,82.43 83.99,81.35 82.66,81.35C81.34,81.35 80.26,82.43 80.26,83.75C80.26,85.08 81.34,86.15 82.66,86.15Z"
+ android:fillColor="#EA4335"/>
+ <path
+ android:pathData="M82.66,92.45C84.21,92.45 85.46,91.2 85.46,89.65C85.46,88.11 84.21,86.85 82.66,86.85C81.11,86.85 79.86,88.11 79.86,89.65C79.86,91.2 81.11,92.45 82.66,92.45Z"
+ android:fillColor="#FBBC04"/>
+ <path
+ android:pathData="M74.76,86.15C77.41,86.15 79.56,84 79.56,81.35C79.56,78.7 77.41,76.55 74.76,76.55C72.11,76.55 69.96,78.7 69.96,81.35C69.96,84 72.11,86.15 74.76,86.15Z"
+ android:fillColor="#4285F4"/>
+ <path
+ android:pathData="M174.71,120.5L174.71,120.5A16,16 0,0 1,190.71 136.5L190.71,136.5A16,16 0,0 1,174.71 152.5L174.71,152.5A16,16 0,0 1,158.71 136.5L158.71,136.5A16,16 0,0 1,174.71 120.5z"
+ android:fillColor="#9F3EBF"/>
+ <path
+ android:pathData="M178.53,130.5H171.03V142.2H178.53V130.5Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M170.23,132.8H167.53V140H170.23V132.8Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M182.23,132.8H179.53V140H182.23V132.8Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M126.71,172.5L126.71,172.5A16,16 0,0 1,142.71 188.5L142.71,188.5A16,16 0,0 1,126.71 204.5L126.71,204.5A16,16 0,0 1,110.71 188.5L110.71,188.5A16,16 0,0 1,126.71 172.5z"
+ android:fillColor="#DE9834"/>
+ <path
+ android:pathData="M135.71,188.5L133.11,191.1V194.8H129.31L126.71,197.4L124.11,194.8H120.31V191.1L117.71,188.5L120.31,185.9V182.2H124.01L126.61,179.6L129.21,182.2H132.91V185.9L135.71,188.5ZM121.91,188.5C121.91,191.1 124.01,193.3 126.71,193.3C129.31,193.3 131.51,191.2 131.51,188.5C131.51,185.8 129.41,183.7 126.71,183.7C124.11,183.7 121.91,185.8 121.91,188.5Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M127.21,187.9V186.4H126.21V187.9H124.71V188.9H126.21V190.4H127.21V188.9H128.71V187.9H127.21Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M126.71,68.5L126.71,68.5A16,16 0,0 1,142.71 84.5L142.71,84.5A16,16 0,0 1,126.71 100.5L126.71,100.5A16,16 0,0 1,110.71 84.5L110.71,84.5A16,16 0,0 1,126.71 68.5z"
+ android:fillColor="#521BBF"/>
+ <path
+ android:pathData="M128.36,78.51C128.36,79.31 127.66,80.01 126.76,80.01C125.86,80.01 125.16,79.31 125.16,78.51C125.16,77.71 125.86,77.01 126.76,77.01C127.66,76.91 128.36,77.61 128.36,78.51ZM126.76,80.71C128.96,80.71 131.46,80.51 133.46,79.91L133.86,81.41C132.36,81.81 130.66,82.01 129.06,82.21V92.01H127.46V87.51H125.86V92.01H124.36V82.21C122.76,82.11 121.06,81.81 119.56,81.41L119.96,79.91C122.06,80.51 124.46,80.71 126.76,80.71Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M174.71,172.5L174.71,172.5A16,16 0,0 1,190.71 188.5L190.71,188.5A16,16 0,0 1,174.71 204.5L174.71,204.5A16,16 0,0 1,158.71 188.5L158.71,188.5A16,16 0,0 1,174.71 172.5z"
+ android:fillColor="#438947"/>
+ <path
+ android:pathData="M179.11,185.95H178.41V184.45C178.41,182.45 176.81,180.75 174.71,180.75C172.61,180.75 171.01,182.35 171.01,184.45V185.95H170.31C169.51,185.95 168.81,186.65 168.81,187.45V194.75C168.81,195.55 169.51,196.25 170.31,196.25H179.11C179.91,196.25 180.61,195.55 180.61,194.75V187.45C180.61,186.65 179.91,185.95 179.11,185.95ZM174.71,192.55C173.91,192.55 173.21,191.85 173.21,191.05C173.21,190.25 173.91,189.55 174.71,189.55C175.51,189.55 176.21,190.25 176.21,191.05C176.21,191.95 175.51,192.55 174.71,192.55ZM172.41,185.95H176.91V184.45C176.91,183.15 175.91,182.15 174.61,182.15C173.31,182.15 172.31,183.15 172.31,184.45V185.95H172.41Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M174.71,68.5L174.71,68.5A16,16 0,0 1,190.71 84.5L190.71,84.5A16,16 0,0 1,174.71 100.5L174.71,100.5A16,16 0,0 1,158.71 84.5L158.71,84.5A16,16 0,0 1,174.71 68.5z"
+ android:fillColor="#80868B"/>
+ <path
+ android:pathData="M173.91,77.35H175.51V85.25H173.91V77.35ZM178.21,80.25L179.31,79.15C180.81,80.45 181.81,82.35 181.81,84.55C181.81,88.45 178.61,91.65 174.71,91.65C170.81,91.65 167.61,88.45 167.61,84.55C167.61,82.35 168.61,80.45 170.11,79.15L171.21,80.25C170.01,81.25 169.21,82.85 169.21,84.55C169.21,87.65 171.71,90.15 174.81,90.15C177.91,90.15 180.41,87.65 180.41,84.55C180.31,82.75 179.41,81.25 178.21,80.25Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ </group>
+ <path
+ android:pathData="M62.23,51.69L349.77,51.69A14.5,14.5 0,0 1,364.27 66.19L364.27,236.14A14.5,14.5 0,0 1,349.77 250.64L62.23,250.64A14.5,14.5 0,0 1,47.73 236.14L47.73,66.19A14.5,14.5 0,0 1,62.23 51.69z"
+ android:strokeWidth="3"
+ android:fillColor="#00000000"
+ android:strokeColor="#DADCE0"/>
+ <path
+ android:pathData="M311.45,50.35C311.45,48.98 312.56,47.87 313.92,47.87L322.84,47.87C324.2,47.87 325.32,48.98 325.32,50.35L319.37,51.34L311.45,50.35Z"
+ android:fillColor="#DADCE0"/>
+ <path
+ android:pathData="M263.59,50.35C263.59,48.98 264.7,47.87 266.06,47.87L287.85,47.87C289.22,47.87 290.33,48.98 290.33,50.35L277.45,51.34L263.59,50.35Z"
+ android:fillColor="#DADCE0"/>
+</vector>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_menu.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_menu.xml
deleted file mode 100644
index 79e0e08d..0000000
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_a11y_menu.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<vector android:height="48dp" android:viewportHeight="192.0"
- android:viewportWidth="192.0" android:width="48dp"
- xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
- <path android:fillColor="#34a853" android:pathData="M37.14,173.74l-28.53,-90a14.53,14.53 0,0 1,5 -15.63L87.15,11a14.21,14.21 0,0 1,17.61 0.12l73.81,58.94a14.53,14.53 0,0 1,4.8 15.57l-28.48,88.18A14.32,14.32 0,0 1,141.22 184H50.84A14.33,14.33 0,0 1,37.14 173.74Z"/>
- <path android:pathData="M137.61,94.07l-17,17 -17,-17 -17,17L70.3,94.72l-17,17L125.66,184h15.56a14.32,14.32 0,0 0,13.67 -10.19l15.25,-47.21Z">
- <aapt:attr name="android:fillColor">
- <gradient android:endX="27152.64"
- android:endY="32745.600000000002"
- android:startX="20910.72"
- android:startY="21934.079999999998" android:type="linear">
- <item android:color="#33263238" android:offset="0.0"/>
- <item android:color="#11205432" android:offset="0.47"/>
- <item android:color="#051E6130" android:offset="1.0"/>
- </gradient>
- </aapt:attr>
- </path>
- <path android:fillAlpha="0.2" android:fillColor="#263238" android:pathData="M50.14,100.11a12,12 0,1 1,11.39 15.77,11.72 11.72,0 0,1 -5,-1.1l-3.41,-3.4ZM129.4,91.88a12,12 0,1 1,-12 12A12,12 0,0 1,129.4 91.88ZM95.4,91.88a12,12 0,1 1,-12 12A12,12 0,0 1,95.42 91.88Z"/>
- <path android:fillColor="#fff"
- android:pathData="M61.53,90.88a12,12 0,1 1,-12 12A12,12 0,0 1,61.53 90.88ZM129.41,90.88a12,12 0,1 1,-12 12A12,12 0,0 1,129.41 90.88ZM95.41,90.88a12,12 0,1 1,-12 12A12,12 0,0 1,95.42 90.88Z"
- android:strokeAlpha="0" android:strokeColor="#000"
- android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0.5"/>
- <path android:fillAlpha="0.2" android:fillColor="#263238" android:pathData="M184,80.91a14.33,14.33 0,0 1,-0.63 4.7l-28.48,88.18A14.33,14.33 0,0 1,141.21 184H50.84a14.33,14.33 0,0 1,-13.7 -10.26l-28.53,-90A14.49,14.49 0,0 1,8 79.11a14.3,14.3 0,0 0,0.61 3.64l28.53,90A14.33,14.33 0,0 0,50.84 183h90.37a14.33,14.33 0,0 0,13.67 -10.19l28.48,-88.18A14.79,14.79 0,0 0,184 80.91Z"/>
- <path android:fillAlpha="0.2" android:fillColor="#fff" android:pathData="M184,81.89A14.46,14.46 0,0 0,178.57 71L104.76,12.1A14.21,14.21 0,0 0,87.15 12L13.58,69.12A14.5,14.5 0,0 0,8 80.09a14.5,14.5 0,0 1,5.57 -12L87.15,11a14.21,14.21 0,0 1,17.61 0.12L178.57,70A14.48,14.48 0,0 1,184 81.89Z"/>
- <path android:pathData="M37.14,173.74l-28.53,-90a14.53,14.53 0,0 1,5 -15.63L87.15,11a14.21,14.21 0,0 1,17.61 0.12l73.81,58.94a14.53,14.53 0,0 1,4.8 15.57l-28.48,88.18A14.32,14.32 0,0 1,141.22 184H50.84A14.33,14.33 0,0 1,37.14 173.74Z"/>
- <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
- <background android:drawable="@color/colorAccessibilityMenuIcon" />
- <foreground>
- <inset
- android:drawable="@drawable/ic_a11y_menu_round"
- android:inset="21.88%" />
- </foreground>
- </adaptive-icon>
-</vector>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_app.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_app.xml
new file mode 100644
index 0000000..31fe99e1f
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_app.xml
@@ -0,0 +1,8 @@
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@color/colorAccessibilityMenuIcon" />
+ <foreground>
+ <inset
+ android:drawable="@drawable/ic_logo_app_foreground"
+ android:inset="21.88%" />
+ </foreground>
+</adaptive-icon>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_a11y_menu_round.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_app_foreground.xml
similarity index 100%
rename from packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_a11y_menu_round.xml
rename to packages/SystemUI/accessibility/accessibilitymenu/res/drawable/ic_logo_app_foreground.xml
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
index 76139c6..ca6c332 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -22,19 +22,28 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
+import android.hardware.display.BrightnessInfo;
import android.hardware.display.DisplayManager;
+import android.media.AudioManager;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
+import android.provider.Settings;
import android.view.Display;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+import com.android.settingslib.display.BrightnessUtils;
+import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut.ShortcutId;
import com.android.systemui.accessibility.accessibilitymenu.view.A11yMenuOverlayLayout;
+import java.util.List;
+
/** @hide */
public class AccessibilityMenuService extends AccessibilityService
implements View.OnTouchListener {
@@ -42,6 +51,11 @@
private static final long BUFFER_MILLISECONDS_TO_PREVENT_UPDATE_FAILURE = 100L;
+ private static final int BRIGHTNESS_UP_INCREMENT_GAMMA =
+ (int) Math.ceil(BrightnessUtils.GAMMA_SPACE_MAX * 0.11f);
+ private static final int BRIGHTNESS_DOWN_INCREMENT_GAMMA =
+ (int) -Math.ceil(BrightnessUtils.GAMMA_SPACE_MAX * 0.11f);
+
private long mLastTimeTouchedOutside = 0L;
// Timeout used to ignore the A11y button onClick() when ACTION_OUTSIDE is also received on
// clicking on the A11y button.
@@ -51,6 +65,8 @@
private static boolean sInitialized = false;
+ private AudioManager mAudioManager;
+
// TODO(b/136716947): Support multi-display once a11y framework side is ready.
private DisplayManager mDisplayManager;
final DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() {
@@ -140,6 +156,7 @@
mDisplayManager = getSystemService(DisplayManager.class);
mDisplayManager.registerDisplayListener(mDisplayListener, null);
+ mAudioManager = getSystemService(AudioManager.class);
sInitialized = true;
}
@@ -170,9 +187,100 @@
* @param view the shortcut button being clicked.
*/
public void handleClick(View view) {
+ // Shortcuts are repeatable in a11y menu rather than unique, so use tag ID to handle.
+ int viewTag = (int) view.getTag();
+
+ if (viewTag == ShortcutId.ID_ASSISTANT_VALUE.ordinal()) {
+ // Always restart the voice command activity, so that the UI is reloaded.
+ startActivityIfIntentIsSafe(
+ new Intent(Intent.ACTION_VOICE_COMMAND),
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ } else if (viewTag == ShortcutId.ID_A11YSETTING_VALUE.ordinal()) {
+ startActivityIfIntentIsSafe(
+ new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS),
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ } else if (viewTag == ShortcutId.ID_POWER_VALUE.ordinal()) {
+ performGlobalAction(GLOBAL_ACTION_POWER_DIALOG);
+ } else if (viewTag == ShortcutId.ID_RECENT_VALUE.ordinal()) {
+ performGlobalAction(GLOBAL_ACTION_RECENTS);
+ } else if (viewTag == ShortcutId.ID_LOCKSCREEN_VALUE.ordinal()) {
+ performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN);
+ } else if (viewTag == ShortcutId.ID_QUICKSETTING_VALUE.ordinal()) {
+ performGlobalAction(GLOBAL_ACTION_QUICK_SETTINGS);
+ } else if (viewTag == ShortcutId.ID_NOTIFICATION_VALUE.ordinal()) {
+ performGlobalAction(GLOBAL_ACTION_NOTIFICATIONS);
+ } else if (viewTag == ShortcutId.ID_SCREENSHOT_VALUE.ordinal()) {
+ performGlobalAction(GLOBAL_ACTION_TAKE_SCREENSHOT);
+ } else if (viewTag == ShortcutId.ID_BRIGHTNESS_UP_VALUE.ordinal()) {
+ adjustBrightness(BRIGHTNESS_UP_INCREMENT_GAMMA);
+ return;
+ } else if (viewTag == ShortcutId.ID_BRIGHTNESS_DOWN_VALUE.ordinal()) {
+ adjustBrightness(BRIGHTNESS_DOWN_INCREMENT_GAMMA);
+ return;
+ } else if (viewTag == ShortcutId.ID_VOLUME_UP_VALUE.ordinal()) {
+ adjustVolume(AudioManager.ADJUST_RAISE);
+ return;
+ } else if (viewTag == ShortcutId.ID_VOLUME_DOWN_VALUE.ordinal()) {
+ adjustVolume(AudioManager.ADJUST_LOWER);
+ return;
+ }
+
mA11yMenuLayout.hideMenu();
}
+ /**
+ * Adjusts brightness using the same logic and utils class as the SystemUI brightness slider.
+ *
+ * @see BrightnessUtils
+ * @see com.android.systemui.settings.brightness.BrightnessController
+ * @param increment The increment amount in gamma-space
+ */
+ private void adjustBrightness(int increment) {
+ BrightnessInfo info = getDisplay().getBrightnessInfo();
+ int gamma = BrightnessUtils.convertLinearToGammaFloat(
+ info.brightness,
+ info.brightnessMinimum,
+ info.brightnessMaximum
+ );
+ gamma = Math.max(
+ BrightnessUtils.GAMMA_SPACE_MIN,
+ Math.min(BrightnessUtils.GAMMA_SPACE_MAX, gamma + increment));
+
+ float brightness = BrightnessUtils.convertGammaToLinearFloat(
+ gamma,
+ info.brightnessMinimum,
+ info.brightnessMaximum
+ );
+ mDisplayManager.setTemporaryBrightness(getDisplayId(), brightness);
+ mDisplayManager.setBrightness(getDisplayId(), brightness);
+ mA11yMenuLayout.showSnackbar(
+ getString(R.string.brightness_percentage_label,
+ (gamma / (BrightnessUtils.GAMMA_SPACE_MAX / 100))));
+ }
+
+ private void adjustVolume(int direction) {
+ mAudioManager.adjustStreamVolume(
+ AudioManager.STREAM_MUSIC, direction,
+ AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
+ final int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ final int volume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ mA11yMenuLayout.showSnackbar(
+ getString(
+ R.string.music_volume_percentage_label,
+ (int) (100.0 / maxVolume * volume))
+ );
+ }
+
+ private void startActivityIfIntentIsSafe(Intent intent, int flag) {
+ PackageManager packageManager = getPackageManager();
+ List<ResolveInfo> activities =
+ packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ if (!activities.isEmpty()) {
+ intent.setFlags(flag);
+ startActivity(intent);
+ }
+ }
+
@Override
public void onInterrupt() {
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
index 4322d53..c764d53 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
@@ -44,6 +44,20 @@
}
// language=AGSL
companion object {
+ // Default fade in/ out values. The value range is [0,1].
+ const val DEFAULT_FADE_IN_START = 0f
+ const val DEFAULT_FADE_OUT_END = 1f
+
+ const val DEFAULT_BASE_RING_FADE_IN_END = 0.1f
+ const val DEFAULT_BASE_RING_FADE_OUT_START = 0.3f
+
+ const val DEFAULT_SPARKLE_RING_FADE_IN_END = 0.1f
+ const val DEFAULT_SPARKLE_RING_FADE_OUT_START = 0.4f
+
+ const val DEFAULT_CENTER_FILL_FADE_IN_END = 0f
+ const val DEFAULT_CENTER_FILL_FADE_OUT_START = 0f
+ const val DEFAULT_CENTER_FILL_FADE_OUT_END = 0.6f
+
private const val SHADER_UNIFORMS =
"""
uniform vec2 in_center;
@@ -73,7 +87,7 @@
* (1.-sparkleRing) * in_fadeSparkle;
float rippleInsideAlpha = (1.-inside) * in_fadeFill;
- float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
+ float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing * in_sparkle_strength;
float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha;
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
@@ -143,11 +157,26 @@
}
private fun subProgress(start: Float, end: Float, progress: Float): Float {
+ // Avoid division by 0.
+ if (start == end) {
+ // If start and end are the same and progress has exceeded the start/ end point,
+ // treat it as 1, otherwise 0.
+ return if (progress > start) 1f else 0f
+ }
+
val min = Math.min(start, end)
val max = Math.max(start, end)
val sub = Math.min(Math.max(progress, min), max)
return (sub - start) / (end - start)
}
+
+ private fun getFade(fadeParams: FadeParams, rawProgress: Float): Float {
+ val fadeIn = subProgress(fadeParams.fadeInStart, fadeParams.fadeInEnd, rawProgress)
+ val fadeOut =
+ 1f - subProgress(fadeParams.fadeOutStart, fadeParams.fadeOutEnd, rawProgress)
+
+ return Math.min(fadeIn, fadeOut)
+ }
}
/** Sets the center position of the ripple. */
@@ -172,17 +201,9 @@
field = value
progress = Interpolators.STANDARD.getInterpolation(value)
- val fadeIn = subProgress(0f, 0.1f, value)
- val fadeOutNoise = subProgress(0.4f, 1f, value)
- var fadeOutRipple = 0f
- var fadeFill = 0f
- if (!rippleFill) {
- fadeFill = subProgress(0f, 0.6f, value)
- fadeOutRipple = subProgress(0.3f, 1f, value)
- }
- setFloatUniform("in_fadeSparkle", Math.min(fadeIn, 1 - fadeOutNoise))
- setFloatUniform("in_fadeFill", 1 - fadeFill)
- setFloatUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
+ setFloatUniform("in_fadeSparkle", getFade(sparkleRingFadeParams, value))
+ setFloatUniform("in_fadeRing", getFade(baseRingFadeParams, value))
+ setFloatUniform("in_fadeFill", getFade(centerFillFadeParams, value))
}
/** Progress with Standard easing curve applied. */
@@ -232,21 +253,130 @@
setFloatUniform("in_distort_xy", 75 * value)
}
+ /**
+ * Pixel density of the screen that the effects are rendered to.
+ *
+ * <p>This value should come from [resources.displayMetrics.density].
+ */
var pixelDensity: Float = 1.0f
set(value) {
field = value
setFloatUniform("in_pixelDensity", value)
}
- /**
- * True if the ripple should stayed filled in as it expands to give a filled-in circle effect.
- * False for a ring effect.
- */
- var rippleFill: Boolean = false
-
var currentWidth: Float = 0f
private set
var currentHeight: Float = 0f
private set
+
+ /**
+ * True if the ripple should stayed filled in as it expands to give a filled-in circle effect.
+ * False for a ring effect.
+ *
+ * <p>You must reset fade params after changing this.
+ *
+ * TODO(b/265326983): Remove this and only expose fade params.
+ */
+ var rippleFill: Boolean = false
+ set(value) {
+ if (value) {
+ baseRingFadeParams.fadeOutStart = 1f
+ baseRingFadeParams.fadeOutEnd = 1f
+
+ centerFillFadeParams.fadeInStart = 0f
+ centerFillFadeParams.fadeInEnd = 0f
+ centerFillFadeParams.fadeOutStart = 1f
+ centerFillFadeParams.fadeOutEnd = 1f
+ } else {
+ // Set back to the original fade parameters.
+ // Ideally this should be set by the client as they know the initial value.
+ baseRingFadeParams.fadeOutStart = DEFAULT_BASE_RING_FADE_OUT_START
+ baseRingFadeParams.fadeOutEnd = DEFAULT_FADE_OUT_END
+
+ centerFillFadeParams.fadeInStart = DEFAULT_FADE_IN_START
+ centerFillFadeParams.fadeInEnd = DEFAULT_CENTER_FILL_FADE_IN_END
+ centerFillFadeParams.fadeOutStart = DEFAULT_CENTER_FILL_FADE_OUT_START
+ centerFillFadeParams.fadeOutEnd = DEFAULT_CENTER_FILL_FADE_OUT_END
+ }
+ field = value
+ }
+
+ /** Parameters that are used to fade in/ out of the sparkle ring. */
+ val sparkleRingFadeParams =
+ FadeParams(
+ DEFAULT_FADE_IN_START,
+ DEFAULT_SPARKLE_RING_FADE_IN_END,
+ DEFAULT_SPARKLE_RING_FADE_OUT_START,
+ DEFAULT_FADE_OUT_END
+ )
+
+ /**
+ * Parameters that are used to fade in/ out of the base ring.
+ *
+ * <p>Note that the shader draws the sparkle ring on top of the base ring.
+ */
+ val baseRingFadeParams =
+ FadeParams(
+ DEFAULT_FADE_IN_START,
+ DEFAULT_BASE_RING_FADE_IN_END,
+ DEFAULT_BASE_RING_FADE_OUT_START,
+ DEFAULT_FADE_OUT_END
+ )
+
+ /**
+ * Parameters that are used to fade in/ out of the center fill.
+ *
+ * <p>Note that if [rippleFill] is set to true, those will be ignored and the center fill will
+ * be always full alpha.
+ */
+ val centerFillFadeParams =
+ FadeParams(
+ DEFAULT_FADE_IN_START,
+ DEFAULT_CENTER_FILL_FADE_IN_END,
+ DEFAULT_CENTER_FILL_FADE_OUT_START,
+ DEFAULT_CENTER_FILL_FADE_OUT_END
+ )
+
+ /**
+ * Parameters used for fade in and outs of the ripple.
+ *
+ * <p>Note that all the fade in/ outs are "linear" progression.
+ * ```
+ * (opacity)
+ * 1
+ * │
+ * maxAlpha ← ――――――――――――
+ * │ / \
+ * │ / \
+ * minAlpha ←――――/ \―――― (alpha change)
+ * │
+ * │
+ * 0 ―――↑―――↑―――――――――↑―――↑――――1 (progress)
+ * fadeIn fadeOut
+ * Start & End Start & End
+ * ```
+ * <p>If no fade in/ out is needed, set [fadeInStart] and [fadeInEnd] to 0; [fadeOutStart] and
+ * [fadeOutEnd] to 1.
+ */
+ data class FadeParams(
+ /**
+ * The starting point of the fade out which ends at [fadeInEnd], given that the animation
+ * goes from 0 to 1.
+ */
+ var fadeInStart: Float = DEFAULT_FADE_IN_START,
+ /**
+ * The endpoint of the fade in when the fade in starts at [fadeInStart], given that the
+ * animation goes from 0 to 1.
+ */
+ var fadeInEnd: Float,
+ /**
+ * The starting point of the fade out which ends at 1, given that the animation goes from 0
+ * to 1.
+ */
+ var fadeOutStart: Float,
+
+ /** The endpoint of the fade out, given that the animation goes from 0 to 1. */
+ var fadeOutEnd: Float = DEFAULT_FADE_OUT_END,
+ )
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 59b4848..a523cf1 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -21,15 +21,14 @@
import android.provider.Settings
import android.util.Log
import androidx.annotation.OpenForTesting
-import com.android.internal.annotations.Keep
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProvider
import com.android.systemui.plugins.ClockProviderPlugin
+import com.android.systemui.plugins.ClockSettings
import com.android.systemui.plugins.PluginListener
import com.android.systemui.plugins.PluginManager
-import org.json.JSONObject
private val TAG = ClockRegistry::class.simpleName
private const val DEBUG = true
@@ -64,34 +63,54 @@
disconnectClocks(plugin)
}
- open var currentClockId: ClockId
+ open var settings: ClockSettings?
get() {
- return try {
+ try {
val json = Settings.Secure.getString(
context.contentResolver,
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
)
if (json == null || json.isEmpty()) {
- return fallbackClockId
+ return null
}
- ClockSetting.deserialize(json).clockId
+ return ClockSettings.deserialize(json)
} catch (ex: Exception) {
- Log.e(TAG, "Failed to parse clock setting", ex)
- fallbackClockId
+ Log.e(TAG, "Failed to parse clock settings", ex)
+ return null
}
}
- set(value) {
+ protected set(value) {
try {
- val json = ClockSetting.serialize(ClockSetting(value, System.currentTimeMillis()))
+ val json = if (value != null) {
+ value._applied_timestamp = System.currentTimeMillis()
+ ClockSettings.serialize(value)
+ } else {
+ ""
+ }
+
Settings.Secure.putString(
context.contentResolver,
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json
)
} catch (ex: Exception) {
- Log.e(TAG, "Failed to set clock setting", ex)
+ Log.e(TAG, "Failed to set clock settings", ex)
}
}
+ private fun mutateSetting(mutator: (ClockSettings) -> Unit) {
+ val settings = this.settings ?: ClockSettings()
+ mutator(settings)
+ this.settings = settings
+ }
+
+ var currentClockId: ClockId
+ get() = settings?.clockId ?: fallbackClockId
+ set(value) { mutateSetting { it.clockId = value } }
+
+ var seedColor: Int?
+ get() = settings?.seedColor
+ set(value) { mutateSetting { it.seedColor = value } }
+
init {
connectClocks(defaultClockProvider)
if (!availableClocks.containsKey(DEFAULT_CLOCK_ID)) {
@@ -194,36 +213,16 @@
return createClock(DEFAULT_CLOCK_ID)!!
}
- private fun createClock(clockId: ClockId): ClockController? =
- availableClocks[clockId]?.provider?.createClock(clockId)
+ private fun createClock(clockId: ClockId): ClockController? {
+ val settings = this.settings ?: ClockSettings()
+ if (clockId != settings.clockId) {
+ settings.clockId = clockId
+ }
+ return availableClocks[clockId]?.provider?.createClock(settings)
+ }
private data class ClockInfo(
val metadata: ClockMetadata,
val provider: ClockProvider
)
-
- @Keep
- data class ClockSetting(
- val clockId: ClockId,
- val _applied_timestamp: Long?
- ) {
- companion object {
- private val KEY_CLOCK_ID = "clockId"
- private val KEY_TIMESTAMP = "_applied_timestamp"
-
- fun serialize(setting: ClockSetting): String {
- return JSONObject()
- .put(KEY_CLOCK_ID, setting.clockId)
- .put(KEY_TIMESTAMP, setting._applied_timestamp)
- .toString()
- }
-
- fun deserialize(jsonStr: String): ClockSetting {
- val json = JSONObject(jsonStr)
- return ClockSetting(
- json.getString(KEY_CLOCK_ID),
- if (!json.isNull(KEY_TIMESTAMP)) json.getLong(KEY_TIMESTAMP) else null)
- }
- }
- }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 7645dec..2a40f5c 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -29,6 +29,7 @@
import com.android.systemui.plugins.ClockEvents
import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockFaceEvents
+import com.android.systemui.plugins.ClockSettings
import com.android.systemui.plugins.log.LogBuffer
import java.io.PrintWriter
import java.util.Locale
@@ -46,6 +47,7 @@
ctx: Context,
private val layoutInflater: LayoutInflater,
private val resources: Resources,
+ private val settings: ClockSettings?,
) : ClockController {
override val smallClock: DefaultClockFaceController
override val largeClock: LargeClockFaceController
@@ -66,12 +68,14 @@
smallClock =
DefaultClockFaceController(
layoutInflater.inflate(R.layout.clock_default_small, parent, false)
- as AnimatableClockView
+ as AnimatableClockView,
+ settings?.seedColor
)
largeClock =
LargeClockFaceController(
layoutInflater.inflate(R.layout.clock_default_large, parent, false)
- as AnimatableClockView
+ as AnimatableClockView,
+ settings?.seedColor
)
clocks = listOf(smallClock.view, largeClock.view)
@@ -85,11 +89,13 @@
animations = DefaultClockAnimations(dozeFraction, foldFraction)
events.onColorPaletteChanged(resources)
events.onTimeZoneChanged(TimeZone.getDefault())
- events.onTimeTick()
+ smallClock.events.onTimeTick()
+ largeClock.events.onTimeTick()
}
open inner class DefaultClockFaceController(
override val view: AnimatableClockView,
+ val seedColor: Int?,
) : ClockFaceController {
// MAGENTA is a placeholder, and will be assigned correctly in initialize
@@ -104,11 +110,16 @@
}
init {
+ if (seedColor != null) {
+ currentColor = seedColor
+ }
view.setColors(currentColor, currentColor)
}
override val events =
object : ClockFaceEvents {
+ override fun onTimeTick() = view.refreshTime()
+
override fun onRegionDarknessChanged(isRegionDark: Boolean) {
this@DefaultClockFaceController.isRegionDark = isRegionDark
updateColor()
@@ -129,7 +140,9 @@
fun updateColor() {
val color =
- if (isRegionDark) {
+ if (seedColor != null) {
+ seedColor
+ } else if (isRegionDark) {
resources.getColor(android.R.color.system_accent1_100)
} else {
resources.getColor(android.R.color.system_accent2_600)
@@ -149,7 +162,8 @@
inner class LargeClockFaceController(
view: AnimatableClockView,
- ) : DefaultClockFaceController(view) {
+ seedColor: Int?,
+ ) : DefaultClockFaceController(view, seedColor) {
override fun recomputePadding(targetRegion: Rect?) {
// We center the view within the targetRegion instead of within the parent
// view by computing the difference and adding that to the padding.
@@ -169,8 +183,6 @@
}
inner class DefaultClockEvents : ClockEvents {
- override fun onTimeTick() = clocks.forEach { it.refreshTime() }
-
override fun onTimeFormatChanged(is24Hr: Boolean) =
clocks.forEach { it.refreshFormat(is24Hr) }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 4c0504b..0fd1b49 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -22,6 +22,7 @@
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProvider
+import com.android.systemui.plugins.ClockSettings
private val TAG = DefaultClockProvider::class.simpleName
const val DEFAULT_CLOCK_NAME = "Default Clock"
@@ -36,12 +37,12 @@
override fun getClocks(): List<ClockMetadata> =
listOf(ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME))
- override fun createClock(id: ClockId): ClockController {
- if (id != DEFAULT_CLOCK_ID) {
- throw IllegalArgumentException("$id is unsupported by $TAG")
+ override fun createClock(settings: ClockSettings): ClockController {
+ if (settings.clockId != DEFAULT_CLOCK_ID) {
+ throw IllegalArgumentException("${settings.clockId} is unsupported by $TAG")
}
- return DefaultClockController(ctx, layoutInflater, resources)
+ return DefaultClockController(ctx, layoutInflater, resources, settings)
}
override fun getClockThumbnail(id: ClockId): Drawable? {
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 e4e9c46..c120876 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
@@ -181,6 +181,9 @@
/** Flag denoting whether the Wallpaper preview should use the full screen UI. */
const val FLAG_NAME_WALLPAPER_FULLSCREEN_PREVIEW = "wallpaper_fullscreen_preview"
+ /** Flag denoting whether the Monochromatic Theme is enabled. */
+ const val FLAG_NAME_MONOCHROMATIC_THEME = "is_monochromatic_theme_enabled"
+
object Columns {
/** String. Unique ID for the flag. */
const val NAME = "name"
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/MaterialDynamicColors.java b/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/MaterialDynamicColors.java
index 813b22a..91f3309 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/MaterialDynamicColors.java
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/MaterialDynamicColors.java
@@ -57,19 +57,19 @@
public static final DynamicColor surfaceDim =
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 6.0 : 87.0);
- public static final DynamicColor surfaceSub2 =
+ public static final DynamicColor surfaceContainerLowest =
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 4.0 : 100.0);
- public static final DynamicColor surfaceSub1 =
+ public static final DynamicColor surfaceContainerLow =
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 10.0 : 96.0);
public static final DynamicColor surfaceContainer =
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 12.0 : 94.0);
- public static final DynamicColor surfaceAdd1 =
+ public static final DynamicColor surfaceContainerHigh =
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 17.0 : 92.0);
- public static final DynamicColor surfaceAdd2 =
+ public static final DynamicColor surfaceContainerHighest =
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 22.0 : 90.0);
public static final DynamicColor onSurface =
@@ -366,6 +366,27 @@
public static final DynamicColor textHintInverse =
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 10.0 : 90.0);
+ public static final DynamicColor primaryPaletteKeyColor =
+ DynamicColor.fromPalette(
+ (s) -> s.primaryPalette, (s) -> s.primaryPalette.getKeyColor().getTone());
+
+ public static final DynamicColor secondaryPaletteKeyColor =
+ DynamicColor.fromPalette(
+ (s) -> s.secondaryPalette, (s) -> s.secondaryPalette.getKeyColor().getTone());
+
+ public static final DynamicColor tertiaryPaletteKeyColor =
+ DynamicColor.fromPalette(
+ (s) -> s.tertiaryPalette, (s) -> s.tertiaryPalette.getKeyColor().getTone());
+
+ public static final DynamicColor neutralPaletteKeyColor =
+ DynamicColor.fromPalette(
+ (s) -> s.neutralPalette, (s) -> s.neutralPalette.getKeyColor().getTone());
+
+ public static final DynamicColor neutralVariantPaletteKeyColor =
+ DynamicColor.fromPalette(
+ (s) -> s.neutralVariantPalette,
+ (s) -> s.neutralVariantPalette.getKeyColor().getTone());
+
private static ViewingConditions viewingConditionsForAlbers(DynamicScheme scheme) {
return ViewingConditions.defaultWithBackgroundLstar(scheme.isDark ? 30.0 : 80.0);
}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/palettes/TonalPalette.java b/packages/SystemUI/monet/src/com/android/systemui/monet/palettes/TonalPalette.java
index 6911666..8e905ea 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/palettes/TonalPalette.java
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/palettes/TonalPalette.java
@@ -26,6 +26,7 @@
*/
public final class TonalPalette {
Map<Integer, Integer> cache;
+ Hct keyColor;
double hue;
double chroma;
@@ -46,7 +47,7 @@
* @return Tones matching that color's hue and chroma.
*/
public static TonalPalette fromHct(Hct hct) {
- return TonalPalette.fromHueAndChroma(hct.getHue(), hct.getChroma());
+ return new TonalPalette(hct.getHue(), hct.getChroma(), hct);
}
/**
@@ -57,13 +58,52 @@
* @return Tones matching hue and chroma.
*/
public static TonalPalette fromHueAndChroma(double hue, double chroma) {
- return new TonalPalette(hue, chroma);
+ return new TonalPalette(hue, chroma, createKeyColor(hue, chroma));
}
- private TonalPalette(double hue, double chroma) {
+ private TonalPalette(double hue, double chroma, Hct keyColor) {
cache = new HashMap<>();
this.hue = hue;
this.chroma = chroma;
+ this.keyColor = keyColor;
+ }
+
+ /** The key color is the first tone, starting from T50, matching the given hue and chroma. */
+ private static Hct createKeyColor(double hue, double chroma) {
+ double startTone = 50.0;
+ Hct smallestDeltaHct = Hct.from(hue, chroma, startTone);
+ double smallestDelta = Math.abs(smallestDeltaHct.getChroma() - chroma);
+ // Starting from T50, check T+/-delta to see if they match the requested
+ // chroma.
+ //
+ // Starts from T50 because T50 has the most chroma available, on
+ // average. Thus it is most likely to have a direct answer and minimize
+ // iteration.
+ for (double delta = 1.0; delta < 50.0; delta += 1.0) {
+ // Termination condition rounding instead of minimizing delta to avoid
+ // case where requested chroma is 16.51, and the closest chroma is 16.49.
+ // Error is minimized, but when rounded and displayed, requested chroma
+ // is 17, key color's chroma is 16.
+ if (Math.round(chroma) == Math.round(smallestDeltaHct.getChroma())) {
+ return smallestDeltaHct;
+ }
+
+ final Hct hctAdd = Hct.from(hue, chroma, startTone + delta);
+ final double hctAddDelta = Math.abs(hctAdd.getChroma() - chroma);
+ if (hctAddDelta < smallestDelta) {
+ smallestDelta = hctAddDelta;
+ smallestDeltaHct = hctAdd;
+ }
+
+ final Hct hctSubtract = Hct.from(hue, chroma, startTone - delta);
+ final double hctSubtractDelta = Math.abs(hctSubtract.getChroma() - chroma);
+ if (hctSubtractDelta < smallestDelta) {
+ smallestDelta = hctSubtractDelta;
+ smallestDeltaHct = hctSubtract;
+ }
+ }
+
+ return smallestDeltaHct;
}
/**
@@ -98,4 +138,9 @@
public double getHue() {
return this.hue;
}
+
+ /** The key color is the first tone, starting from T50, that matches the palette's chroma. */
+ public Hct getKeyColor() {
+ return this.keyColor;
+ }
}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeExpressive.java b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeExpressive.java
index e7c6f79..234826e 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeExpressive.java
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeExpressive.java
@@ -34,14 +34,16 @@
isDark,
contrastLevel,
TonalPalette.fromHueAndChroma(
- MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 120.0), 40.0),
+ MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 240.0), 40.0),
TonalPalette.fromHueAndChroma(
DynamicScheme.getRotatedHue(sourceColorHct, HUES, SECONDARY_ROTATIONS),
24.0),
TonalPalette.fromHueAndChroma(
DynamicScheme.getRotatedHue(sourceColorHct, HUES, TERTIARY_ROTATIONS),
32.0),
- TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 8.0),
- TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 12.0));
+ TonalPalette.fromHueAndChroma(
+ MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 15.0), 8.0),
+ TonalPalette.fromHueAndChroma(
+ MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 15.0), 12.0));
}
}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeTonalSpot.java b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeTonalSpot.java
index cd74ac3..8480684 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeTonalSpot.java
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeTonalSpot.java
@@ -28,11 +28,11 @@
Variant.TONAL_SPOT,
isDark,
contrastLevel,
- TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 40.0),
+ TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 36.0),
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 16.0),
TonalPalette.fromHueAndChroma(
MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 60.0), 24.0),
- TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 6.0),
+ TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 4.0),
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 8.0));
}
}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeVibrant.java b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeVibrant.java
index 89337ca..160fab7 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeVibrant.java
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeVibrant.java
@@ -38,7 +38,7 @@
TonalPalette.fromHueAndChroma(
DynamicScheme.getRotatedHue(sourceColorHct, HUES, TERTIARY_ROTATIONS),
32.0),
- TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 8.0),
+ TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 10.0),
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 12.0));
}
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index a2a0709..bbfb6fb 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -17,11 +17,13 @@
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.view.View
+import com.android.internal.annotations.Keep
import com.android.systemui.plugins.annotations.ProvidesInterface
import com.android.systemui.plugins.log.LogBuffer
import java.io.PrintWriter
import java.util.Locale
import java.util.TimeZone
+import org.json.JSONObject
/** Identifies a clock design */
typealias ClockId = String
@@ -41,7 +43,13 @@
fun getClocks(): List<ClockMetadata>
/** Initializes and returns the target clock design */
- fun createClock(id: ClockId): ClockController
+ @Deprecated("Use overload with ClockSettings")
+ fun createClock(id: ClockId): ClockController {
+ return createClock(ClockSettings(id, null, null))
+ }
+
+ /** Initializes and returns the target clock design */
+ fun createClock(settings: ClockSettings): ClockController
/** A static thumbnail for rendering in some examples */
fun getClockThumbnail(id: ClockId): Drawable?
@@ -62,11 +70,16 @@
val animations: ClockAnimations
/** Initializes various rendering parameters. If never called, provides reasonable defaults. */
- fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
+ fun initialize(
+ resources: Resources,
+ dozeFraction: Float,
+ foldFraction: Float,
+ ) {
events.onColorPaletteChanged(resources)
animations.doze(dozeFraction)
animations.fold(foldFraction)
- events.onTimeTick()
+ smallClock.events.onTimeTick()
+ largeClock.events.onTimeTick()
}
/** Optional method for dumping debug information */
@@ -87,9 +100,6 @@
/** Events that should call when various rendering parameters change */
interface ClockEvents {
- /** Call every time tick */
- fun onTimeTick() {}
-
/** Call whenever timezone changes */
fun onTimeZoneChanged(timeZone: TimeZone) {}
@@ -131,6 +141,13 @@
/** Events that have specific data about the related face */
interface ClockFaceEvents {
+ /** Call every time tick */
+ fun onTimeTick() {}
+
+ /** Expected interval between calls to onTimeTick. Can always reduce to PER_MINUTE in AOD. */
+ val tickRate: ClockTickRate
+ get() = ClockTickRate.PER_MINUTE
+
/** Region Darkness specific to the clock face */
fun onRegionDarknessChanged(isDark: Boolean) {}
@@ -150,8 +167,46 @@
fun onTargetRegionChanged(targetRegion: Rect?) {}
}
+/** Tick rates for clocks */
+enum class ClockTickRate(val value: Int) {
+ PER_MINUTE(2), // Update the clock once per minute.
+ PER_SECOND(1), // Update the clock once per second.
+ PER_FRAME(0), // Update the clock every second.
+}
+
/** Some data about a clock design */
data class ClockMetadata(
val clockId: ClockId,
val name: String,
)
+
+/** Structure for keeping clock-specific settings */
+@Keep
+data class ClockSettings(
+ var clockId: ClockId? = null,
+ var seedColor: Int? = null,
+ var _applied_timestamp: Long? = null,
+) {
+ companion object {
+ private val KEY_CLOCK_ID = "clockId"
+ private val KEY_SEED_COLOR = "seedColor"
+ private val KEY_TIMESTAMP = "_applied_timestamp"
+
+ fun serialize(setting: ClockSettings): String {
+ return JSONObject()
+ .put(KEY_CLOCK_ID, setting.clockId)
+ .put(KEY_SEED_COLOR, setting.seedColor)
+ .put(KEY_TIMESTAMP, setting._applied_timestamp)
+ .toString()
+ }
+
+ fun deserialize(jsonStr: String): ClockSettings {
+ val json = JSONObject(jsonStr)
+ return ClockSettings(
+ json.getString(KEY_CLOCK_ID),
+ if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null,
+ if (!json.isNull(KEY_TIMESTAMP)) json.getLong(KEY_TIMESTAMP) else null
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt
new file mode 100644
index 0000000..85ec42d
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt
@@ -0,0 +1,85 @@
+package com.android.systemui.statusbar
+
+import android.os.Bundle
+
+class Weather(val conditions: WeatherStateIcon, val temperature: Int, val isCelsius: Boolean) {
+ companion object {
+ private const val TAG = "Weather"
+ private const val WEATHER_STATE_ICON_KEY = "weather_state_icon_extra_key"
+ private const val TEMPERATURE_VALUE_KEY = "temperature_value_extra_key"
+ private const val TEMPERATURE_UNIT_KEY = "temperature_unit_extra_key"
+ private const val INVALID_TEMPERATURE = Int.MIN_VALUE
+
+ fun fromBundle(extras: Bundle): Weather? {
+ val icon =
+ WeatherStateIcon.fromInt(
+ extras.getInt(WEATHER_STATE_ICON_KEY, WeatherStateIcon.UNKNOWN_ICON.id)
+ )
+ if (icon == null || icon == WeatherStateIcon.UNKNOWN_ICON) {
+ return null
+ }
+ val temperature = extras.getInt(TEMPERATURE_VALUE_KEY, INVALID_TEMPERATURE)
+ if (temperature == INVALID_TEMPERATURE) {
+ return null
+ }
+ return Weather(icon, temperature, extras.getBoolean(TEMPERATURE_UNIT_KEY))
+ }
+ }
+
+ enum class WeatherStateIcon(val id: Int) {
+ UNKNOWN_ICON(0),
+
+ // Clear, day & night.
+ SUNNY(1),
+ CLEAR_NIGHT(2),
+
+ // Mostly clear, day & night.
+ MOSTLY_SUNNY(3),
+ MOSTLY_CLEAR_NIGHT(4),
+
+ // Partly cloudy, day & night.
+ PARTLY_CLOUDY(5),
+ PARTLY_CLOUDY_NIGHT(6),
+
+ // Mostly cloudy, day & night.
+ MOSTLY_CLOUDY_DAY(7),
+ MOSTLY_CLOUDY_NIGHT(8),
+ CLOUDY(9),
+ HAZE_FOG_DUST_SMOKE(10),
+ DRIZZLE(11),
+ HEAVY_RAIN(12),
+ SHOWERS_RAIN(13),
+
+ // Scattered showers, day & night.
+ SCATTERED_SHOWERS_DAY(14),
+ SCATTERED_SHOWERS_NIGHT(15),
+
+ // Isolated scattered thunderstorms, day & night.
+ ISOLATED_SCATTERED_TSTORMS_DAY(16),
+ ISOLATED_SCATTERED_TSTORMS_NIGHT(17),
+ STRONG_TSTORMS(18),
+ BLIZZARD(19),
+ BLOWING_SNOW(20),
+ FLURRIES(21),
+ HEAVY_SNOW(22),
+
+ // Scattered snow showers, day & night.
+ SCATTERED_SNOW_SHOWERS_DAY(23),
+ SCATTERED_SNOW_SHOWERS_NIGHT(24),
+ SNOW_SHOWERS_SNOW(25),
+ MIXED_RAIN_HAIL_RAIN_SLEET(26),
+ SLEET_HAIL(27),
+ TORNADO(28),
+ TROPICAL_STORM_HURRICANE(29),
+ WINDY_BREEZY(30),
+ WINTRY_MIX_RAIN_SNOW(31);
+
+ companion object {
+ fun fromInt(value: Int) = values().firstOrNull { it.id == value }
+ }
+ }
+
+ override fun toString(): String {
+ return "$conditions $temperature${if (isCelsius) "C" else "F"}"
+ }
+}
diff --git a/packages/SystemUI/res/drawable/accessibility_window_magnification_background.xml b/packages/SystemUI/res/drawable/accessibility_window_magnification_background.xml
new file mode 100644
index 0000000..58fe368
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_window_magnification_background.xml
@@ -0,0 +1,34 @@
+<?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.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/magnifier_corner_radius" />
+ <stroke
+ android:color="@color/magnification_border_color"
+ android:width="@dimen/magnifier_border_width"/>
+ </shape>
+ </item>
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/magnifier_outer_corner_radius" />
+ <stroke
+ android:color="@android:color/black"
+ android:width="@dimen/magnifier_stroke_width"/>
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/accessibility_window_magnification_background_change.xml b/packages/SystemUI/res/drawable/accessibility_window_magnification_background_change.xml
new file mode 100644
index 0000000..693545b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_window_magnification_background_change.xml
@@ -0,0 +1,36 @@
+<?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.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/magnifier_edit_corner_radius" />
+ <stroke
+ android:color="@color/magnification_border_color"
+ android:width="@dimen/magnifier_border_width"
+ android:dashWidth="@dimen/magnifier_edit_dash_gap"
+ android:dashGap="@dimen/magnifier_edit_dash_gap"/>
+ </shape>
+ </item>
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/magnifier_edit_outer_corner_radius" />
+ <stroke
+ android:color="@android:color/black"
+ android:width="@dimen/magnifier_stroke_width"/>
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_corner_background.xml b/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_corner_background.xml
new file mode 100644
index 0000000..c8cd4c9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_corner_background.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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <size
+ android:width="@dimen/magnification_window_drag_corner_size"
+ android:height="@dimen/magnification_window_drag_corner_size"/>
+ <stroke
+ android:color="@android:color/black"
+ android:width="@dimen/magnification_window_drag_corner_stroke"/>
+ <solid android:color="@color/magnification_drag_corner_background" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_handle_background.xml b/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_handle_background.xml
new file mode 100644
index 0000000..a52e805
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_handle_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"
+ android:shape="rectangle">
+ <stroke
+ android:color="@android:color/black"
+ android:width="@dimen/magnifier_stroke_width"/>
+ <corners android:radius="@dimen/magnifier_corner_radius" />
+ <solid android:color="@color/magnification_border_color" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/clipboard_minimized_background.xml b/packages/SystemUI/res/drawable/clipboard_minimized_background.xml
new file mode 100644
index 0000000..a179c14
--- /dev/null
+++ b/packages/SystemUI/res/drawable/clipboard_minimized_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">
+ <solid android:color="?androidprv:attr/colorAccentSecondary"/>
+ <corners android:radius="10dp"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/dream_overlay_assistant_attention_indicator.xml b/packages/SystemUI/res/drawable/dream_overlay_assistant_attention_indicator.xml
new file mode 100644
index 0000000..dad2cdf8e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/dream_overlay_assistant_attention_indicator.xml
@@ -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.
+ -->
+
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="56dp"
+ android:height="24dp"
+ android:viewportWidth="56"
+ android:viewportHeight="24">
+ <group>
+ <clip-path
+ android:pathData="M12 0H44C50.6274 0 56 5.37258 56 12C56 18.6274 50.6274 24 44 24H12C5.37258 24 0 18.6274 0 12C0 5.37258 5.37258 0 12 0Z"
+ />
+ <path
+ android:pathData="M0 0V24H56V0"
+ android:fillColor="#FFFFFF"
+ />
+ </group>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_arrow_top_left_and_bottom_right.xml b/packages/SystemUI/res/drawable/ic_arrow_top_left_and_bottom_right.xml
new file mode 100644
index 0000000..9cac38b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_arrow_top_left_and_bottom_right.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ 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="12dp"
+ android:height="12dp"
+ android:viewportWidth="12"
+ android:viewportHeight="12">
+ <path
+ android:pathData="M4.304,5.717L8.731,10.141L6.903,11.969L11.959,11.953L11.973,6.899L10.131,8.74L5.703,4.318L3.229,1.843L5.073,0L0,0L0,5.071L1.83,3.243L4.304,5.717Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_arrow_top_right_and_bottom_left.xml b/packages/SystemUI/res/drawable/ic_arrow_top_right_and_bottom_left.xml
new file mode 100644
index 0000000..0a87441
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_arrow_top_right_and_bottom_left.xml
@@ -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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="12dp"
+ android:height="12dp"
+ android:viewportWidth="12"
+ android:viewportHeight="12">
+ <group
+ android:pivotX="6"
+ android:pivotY="6"
+ android:rotation="90">
+ <path
+ android:pathData="M4.304,5.717L8.731,10.141L6.903,11.969L11.959,11.953L11.973,6.899L10.131,8.74L5.703,4.318L3.229,1.843L5.073,0L0,0L0,5.071L1.83,3.243L4.304,5.717Z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+ </group>
+</vector>
+
+
diff --git a/packages/SystemUI/res/drawable/ic_content_paste.xml b/packages/SystemUI/res/drawable/ic_content_paste.xml
new file mode 100644
index 0000000..8c8b81e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_content_paste.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M9,42Q7.7,42 6.85,41.15Q6,40.3 6,39V9Q6,7.7 6.85,6.85Q7.7,6 9,6H19.1Q19.45,4.25 20.825,3.125Q22.2,2 24,2Q25.8,2 27.175,3.125Q28.55,4.25 28.9,6H39Q40.3,6 41.15,6.85Q42,7.7 42,9V39Q42,40.3 41.15,41.15Q40.3,42 39,42ZM9,39H39Q39,39 39,39Q39,39 39,39V9Q39,9 39,9Q39,9 39,9H36V13.5H12V9H9Q9,9 9,9Q9,9 9,9V39Q9,39 9,39Q9,39 9,39ZM24,9Q24.85,9 25.425,8.425Q26,7.85 26,7Q26,6.15 25.425,5.575Q24.85,5 24,5Q23.15,5 22.575,5.575Q22,6.15 22,7Q22,7.85 22.575,8.425Q23.15,9 24,9Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_move_magnification.xml b/packages/SystemUI/res/drawable/ic_move_magnification.xml
index dfedd28..641bb43 100644
--- a/packages/SystemUI/res/drawable/ic_move_magnification.xml
+++ b/packages/SystemUI/res/drawable/ic_move_magnification.xml
@@ -20,6 +20,6 @@
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
- android:fillColor="@android:color/white"
+ android:fillColor="@color/magnification_drag_handle_tint"
android:pathData="M12,15Q10.75,15 9.875,14.125Q9,13.25 9,12Q9,10.75 9.875,9.875Q10.75,9 12,9Q13.25,9 14.125,9.875Q15,10.75 15,12Q15,13.25 14.125,14.125Q13.25,15 12,15ZM12,22 L7.75,17.75 9.15,16.35 12,19.15 14.85,16.35 16.25,17.75ZM6.25,16.25 L2,12 6.25,7.75 7.65,9.15 4.85,12 7.65,14.85ZM9.15,7.65 L7.75,6.25 12,2 16.25,6.25 14.85,7.65 12,4.85ZM17.75,16.25 L16.35,14.85 19.15,12 16.35,9.15 17.75,7.75 22,12Z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/media_output_status_help.xml b/packages/SystemUI/res/drawable/media_output_status_help.xml
new file mode 100644
index 0000000..f93c07ec
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_output_status_help.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ 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="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@color/media_dialog_item_main_content"
+ android:pathData="M11.95,18Q12.475,18 12.838,17.637Q13.2,17.275 13.2,16.75Q13.2,16.225 12.838,15.863Q12.475,15.5 11.95,15.5Q11.425,15.5 11.062,15.863Q10.7,16.225 10.7,16.75Q10.7,17.275 11.062,17.637Q11.425,18 11.95,18ZM11.05,14.15H12.9Q12.9,13.325 13.088,12.85Q13.275,12.375 14.15,11.55Q14.8,10.9 15.175,10.312Q15.55,9.725 15.55,8.9Q15.55,7.5 14.525,6.75Q13.5,6 12.1,6Q10.675,6 9.788,6.75Q8.9,7.5 8.55,8.55L10.2,9.2Q10.325,8.75 10.763,8.225Q11.2,7.7 12.1,7.7Q12.9,7.7 13.3,8.137Q13.7,8.575 13.7,9.1Q13.7,9.6 13.4,10.037Q13.1,10.475 12.65,10.85Q11.55,11.825 11.3,12.325Q11.05,12.825 11.05,14.15ZM12,22Q9.925,22 8.1,21.212Q6.275,20.425 4.925,19.075Q3.575,17.725 2.788,15.9Q2,14.075 2,12Q2,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22Z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/app_clips_screenshot.xml b/packages/SystemUI/res/layout/app_clips_screenshot.xml
new file mode 100644
index 0000000..5155b77
--- /dev/null
+++ b/packages/SystemUI/res/layout/app_clips_screenshot.xml
@@ -0,0 +1,105 @@
+<?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.
+ -->
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:background="@null"
+ android:id="@+id/root"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button
+ android:id="@+id/save"
+ style="@android:style/Widget.DeviceDefault.Button.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:text="@string/app_clips_save_add_to_note"
+ android:layout_marginStart="8dp"
+ android:background="@drawable/overlay_button_background"
+ android:textColor="?android:textColorSecondary"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/preview" />
+
+ <Button
+ android:id="@+id/cancel"
+ style="@android:style/Widget.DeviceDefault.Button.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:text="@android:string/cancel"
+ android:layout_marginStart="6dp"
+ android:background="@drawable/overlay_button_background"
+ android:textColor="?android:textColorSecondary"
+ app:layout_constraintStart_toEndOf="@id/save"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/preview" />
+
+ <ImageView
+ android:id="@+id/preview"
+ android:layout_width="0px"
+ android:layout_height="0px"
+ android:paddingHorizontal="48dp"
+ android:paddingTop="8dp"
+ android:paddingBottom="42dp"
+ android:contentDescription="@string/screenshot_preview_description"
+ app:layout_constrainedHeight="true"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintTop_toBottomOf="@id/save"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ tools:background="?android:colorBackground"
+ tools:minHeight="100dp"
+ tools:minWidth="100dp" />
+
+ <com.android.systemui.screenshot.CropView
+ android:id="@+id/crop_view"
+ android:layout_width="0px"
+ android:layout_height="0px"
+ android:paddingTop="8dp"
+ android:paddingBottom="42dp"
+ app:layout_constrainedHeight="true"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintTop_toTopOf="@id/preview"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:handleThickness="@dimen/screenshot_crop_handle_thickness"
+ app:handleColor="?android:attr/colorAccent"
+ app:scrimColor="?android:colorBackgroundFloating"
+ app:scrimAlpha="128"
+ app:containerBackgroundColor="?android:colorBackgroundFloating"
+ tools:background="?android:colorBackground"
+ tools:minHeight="100dp"
+ tools:minWidth="100dp" />
+
+ <com.android.systemui.screenshot.MagnifierView
+ android:id="@+id/magnifier"
+ android:visibility="invisible"
+ android:layout_width="200dp"
+ android:layout_height="200dp"
+ android:elevation="2dp"
+ app:layout_constraintTop_toTopOf="@id/preview"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:handleThickness="@dimen/screenshot_crop_handle_thickness"
+ app:handleColor="?android:attr/colorAccent"
+ app:scrimColor="?android:colorBackgroundFloating"
+ app:scrimAlpha="128"
+ app:borderThickness="4dp"
+ app:borderColor="#fff" />
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index eec3b11..9b01bd8 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -125,6 +125,45 @@
android:layout_width="@dimen/clipboard_preview_size"
android:layout_height="@dimen/clipboard_preview_size"/>
</FrameLayout>
+ <LinearLayout
+ android:id="@+id/minimized_preview"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:elevation="7dp"
+ android:padding="8dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+ android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
+ android:background="@drawable/clipboard_minimized_background">
+ <ImageView
+ android:src="@drawable/ic_content_paste"
+ android:tint="?attr/overlayButtonTextColor"
+ android:layout_width="24dp"
+ android:layout_height="24dp"/>
+ <ImageView
+ android:src="@*android:drawable/ic_chevron_end"
+ android:tint="?attr/overlayButtonTextColor"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:paddingEnd="-8dp"
+ android:paddingStart="-4dp"/>
+ </LinearLayout>
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/clipboard_content_top"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:barrierDirection="top"
+ app:constraint_referenced_ids="clipboard_preview,minimized_preview"/>
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/clipboard_content_end"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:barrierDirection="end"
+ app:constraint_referenced_ids="clipboard_preview,minimized_preview"/>
<FrameLayout
android:id="@+id/dismiss_button"
android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
@@ -132,10 +171,10 @@
android:elevation="10dp"
android:visibility="gone"
android:alpha="0"
- app:layout_constraintStart_toEndOf="@id/clipboard_preview"
- app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
- app:layout_constraintTop_toTopOf="@id/clipboard_preview"
- app:layout_constraintBottom_toTopOf="@id/clipboard_preview"
+ app:layout_constraintStart_toEndOf="@id/clipboard_content_end"
+ app:layout_constraintEnd_toEndOf="@id/clipboard_content_end"
+ app:layout_constraintTop_toTopOf="@id/clipboard_content_top"
+ app:layout_constraintBottom_toTopOf="@id/clipboard_content_top"
android:contentDescription="@string/clipboard_dismiss_description">
<ImageView
android:id="@+id/dismiss_image"
diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml
index ee3adba..ded6f93 100644
--- a/packages/SystemUI/res/layout/controls_with_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_with_favorites.xml
@@ -91,7 +91,6 @@
android:layout_marginLeft="@dimen/global_actions_side_margin"
android:layout_marginRight="@dimen/global_actions_side_margin"
android:background="@drawable/controls_panel_background"
- android:padding="@dimen/global_actions_side_margin"
android:visibility="gone"
/>
</merge>
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
index 885e5e2..50dcaf3 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -111,5 +111,15 @@
android:visibility="gone"
android:contentDescription="@string/dream_overlay_status_bar_camera_mic_off" />
+ <ImageView
+ android:id="@+id/dream_overlay_assistant_attention_indicator"
+ android:layout_width="@dimen/dream_overlay_grey_chip_width"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/dream_overlay_status_icon_margin"
+ android:src="@drawable/dream_overlay_assistant_attention_indicator"
+ android:visibility="gone"
+ android:contentDescription=
+ "@string/dream_overlay_status_bar_assistant_attention_indicator" />
+
</LinearLayout>
</com.android.systemui.dreams.DreamOverlayStatusBarView>
diff --git a/packages/SystemUI/res/layout/media_recommendation_view.xml b/packages/SystemUI/res/layout/media_recommendation_view.xml
index 101fad9..c54c4e4 100644
--- a/packages/SystemUI/res/layout/media_recommendation_view.xml
+++ b/packages/SystemUI/res/layout/media_recommendation_view.xml
@@ -47,7 +47,8 @@
android:singleLine="true"
android:textSize="12sp"
android:gravity="top"
- android:layout_gravity="bottom"/>
+ android:layout_gravity="bottom"
+ android:importantForAccessibility="no"/>
<!-- Album name -->
<TextView
@@ -61,5 +62,26 @@
android:singleLine="true"
android:textSize="11sp"
android:gravity="center_vertical"
- android:layout_gravity="bottom"/>
+ android:layout_gravity="bottom"
+ android:importantForAccessibility="no"/>
+
+ <!-- Seek Bar -->
+ <SeekBar
+ android:id="@+id/media_progress_bar"
+ android:layout_width="match_parent"
+ android:layout_height="12dp"
+ android:layout_gravity="bottom"
+ android:maxHeight="@dimen/qs_media_enabled_seekbar_height"
+ android:thumb="@android:color/transparent"
+ android:splitTrack="false"
+ android:clickable="false"
+ android:progressTint="?android:attr/textColorPrimary"
+ android:progressBackgroundTint="?android:attr/textColorTertiary"
+ android:paddingTop="5dp"
+ android:paddingBottom="5dp"
+ android:paddingStart="0dp"
+ android:paddingEnd="0dp"
+ android:layout_marginEnd="@dimen/qs_media_info_spacing"
+ android:layout_marginStart="@dimen/qs_media_info_spacing"
+ android:layout_marginBottom="@dimen/qs_media_info_spacing"/>
</merge>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
index c181724..392d845 100644
--- a/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
+++ b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
@@ -4,12 +4,16 @@
android:id="@+id/work_profile_first_run"
android:layout_height="wrap_content"
android:layout_width="match_parent"
+ android:paddingStart="16dp"
+ android:paddingEnd="4dp"
+ android:paddingVertical="16dp"
android:visibility="gone">
<ImageView
android:id="@+id/screenshot_message_icon"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:paddingEnd="4dp"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_marginEnd="12dp"
+ android:layout_gravity="center_vertical"
android:src="@drawable/ic_work_app_badge"/>
<TextView
@@ -17,7 +21,11 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:layout_gravity="start"/>
+ android:layout_gravity="start|center_vertical"
+ android:textSize="18sp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:lineHeight="24sp"
+ />
<FrameLayout
android:id="@+id/message_dismiss_button"
@@ -25,9 +33,9 @@
android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
android:contentDescription="@string/screenshot_dismiss_work_profile">
<ImageView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_margin="@dimen/overlay_dismiss_button_margin"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center"
android:src="@drawable/overlay_cancel"/>
</FrameLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/seekbar_with_icon_buttons.xml b/packages/SystemUI/res/layout/seekbar_with_icon_buttons.xml
new file mode 100644
index 0000000..f20b582
--- /dev/null
+++ b/packages/SystemUI/res/layout/seekbar_with_icon_buttons.xml
@@ -0,0 +1,60 @@
+<?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.
+ -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/seekbar_frame"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ tools:parentTag="android.widget.LinearLayout">
+
+ <ImageView
+ android:id="@+id/icon_start"
+ android:layout_width="@dimen/magnification_setting_seekbar_icon_size"
+ android:layout_height="@dimen/magnification_setting_seekbar_icon_size"
+ android:layout_gravity="center"
+ android:background="?android:attr/selectableItemBackgroundBorderless"
+ android:adjustViewBounds="true"
+ android:focusable="true"
+ android:src="@drawable/ic_remove"
+ android:tint="?android:attr/textColorPrimary"
+ android:tintMode="src_in" />
+
+ <SeekBar
+ android:id="@+id/seekbar"
+ style="@android:style/Widget.Material.SeekBar.Discrete"
+ android:layout_width="0dp"
+ android:layout_height="48dp"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1" />
+
+ <ImageView
+ android:id="@+id/icon_end"
+ android:layout_width="@dimen/magnification_setting_seekbar_icon_size"
+ android:layout_height="@dimen/magnification_setting_seekbar_icon_size"
+ android:layout_gravity="center"
+ android:background="?android:attr/selectableItemBackgroundBorderless"
+ android:adjustViewBounds="true"
+ android:focusable="true"
+ android:src="@drawable/ic_add"
+ android:tint="?android:attr/textColorPrimary"
+ android:tintMode="src_in" />
+
+</merge>
diff --git a/packages/SystemUI/res/layout/window_magnification_settings_view.xml b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
index 377eb97..7dfe7c4 100644
--- a/packages/SystemUI/res/layout/window_magnification_settings_view.xml
+++ b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
@@ -15,6 +15,7 @@
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/magnifier_panel_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -129,13 +130,19 @@
android:layout_weight="1"
android:singleLine="true"
android:text="@string/accessibility_allow_diagonal_scrolling"
- android:textAppearance="@style/TextAppearance.MagnificationSetting.Title" />
+ android:textAppearance="@style/TextAppearance.MagnificationSetting.Title"
+ android:layout_gravity="center_vertical" />
<Switch
android:id="@+id/magnifier_horizontal_lock_switch"
- android:layout_width="wrap_content"
+ android:switchMinWidth="@dimen/settingslib_switch_track_width"
+ android:layout_gravity="right|center_vertical"
+ android:layout_width="@dimen/settingslib_switch_track_width"
android:layout_height="wrap_content"
- android:layout_gravity="right" />
+ android:track="@drawable/settingslib_track_selector"
+ android:thumb="@drawable/settingslib_thumb_selector"
+ android:theme="@style/MainSwitch.Settingslib"/>
+
</LinearLayout>
<TextView
@@ -145,13 +152,14 @@
android:textAppearance="@style/TextAppearance.MagnificationSetting.Title"
android:focusable="true" />
- <SeekBar
- android:id="@+id/magnifier_zoom_seekbar"
- android:layout_height="wrap_content"
+ <com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
+ android:id="@+id/magnifier_zoom_slider"
android:layout_width="match_parent"
- android:layout_marginTop="@dimen/magnification_setting_seekbar_margin"
- android:progress="0"
- android:max="6" />
+ android:layout_height="wrap_content"
+ app:max="6"
+ app:progress="0"
+ app:iconStartContentDescription="@string/accessibility_control_zoom_out"
+ app:iconEndContentDescription="@string/accessibility_control_zoom_in"/>
<Button
android:id="@+id/magnifier_done_button"
diff --git a/packages/SystemUI/res/layout/window_magnifier_view.xml b/packages/SystemUI/res/layout/window_magnifier_view.xml
index 0be7328..a8a048d 100644
--- a/packages/SystemUI/res/layout/window_magnifier_view.xml
+++ b/packages/SystemUI/res/layout/window_magnifier_view.xml
@@ -16,6 +16,7 @@
-->
<FrameLayout 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">
@@ -24,7 +25,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/magnification_outer_border_margin"
- android:background="@color/magnification_border_color"/>
+ android:background="@drawable/accessibility_window_magnification_background"/>
<RelativeLayout
android:layout_width="match_parent"
@@ -66,54 +67,50 @@
<ImageView
android:id="@+id/top_right_corner"
+ android:background="@drawable/accessibility_window_magnification_drag_corner_background"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_margin="@dimen/magnification_outer_border_margin"
+ android:layout_margin="@dimen/magnification_drag_corner_margin"
android:layout_gravity="right|top"
- android:paddingTop="@dimen/magnifier_drag_handle_padding"
- android:paddingEnd="@dimen/magnifier_drag_handle_padding"
android:scaleType="center"
android:contentDescription="@string/magnification_drag_corner_to_resize"
- android:src="@drawable/ic_magnification_corner_top_right"
+ android:src="@drawable/ic_arrow_top_right_and_bottom_left"
android:accessibilityTraversalAfter="@id/top_left_corner"/>
<ImageView
android:id="@+id/top_left_corner"
+ android:background="@drawable/accessibility_window_magnification_drag_corner_background"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_margin="@dimen/magnification_outer_border_margin"
+ android:layout_margin="@dimen/magnification_drag_corner_margin"
android:layout_gravity="left|top"
- android:paddingTop="@dimen/magnifier_drag_handle_padding"
- android:paddingStart="@dimen/magnifier_drag_handle_padding"
android:scaleType="center"
android:contentDescription="@string/magnification_drag_corner_to_resize"
- android:src="@drawable/ic_magnification_corner_top_left"
+ android:src="@drawable/ic_arrow_top_left_and_bottom_right"
android:accessibilityTraversalAfter="@id/bottom_handle"/>
<ImageView
android:id="@+id/bottom_right_corner"
+ android:background="@drawable/accessibility_window_magnification_drag_corner_background"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_margin="@dimen/magnification_outer_border_margin"
+ android:layout_margin="@dimen/magnification_drag_corner_margin"
android:layout_gravity="right|bottom"
- android:paddingEnd="@dimen/magnifier_drag_handle_padding"
- android:paddingBottom="@dimen/magnifier_drag_handle_padding"
android:scaleType="center"
android:contentDescription="@string/magnification_drag_corner_to_resize"
- android:src="@drawable/ic_magnification_corner_bottom_right"
+ android:src="@drawable/ic_arrow_top_left_and_bottom_right"
android:accessibilityTraversalAfter="@id/top_right_corner"/>
<ImageView
android:id="@+id/bottom_left_corner"
+ android:background="@drawable/accessibility_window_magnification_drag_corner_background"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_margin="@dimen/magnification_outer_border_margin"
+ android:layout_margin="@dimen/magnification_drag_corner_margin"
android:layout_gravity="left|bottom"
- android:paddingStart="@dimen/magnifier_drag_handle_padding"
- android:paddingBottom="@dimen/magnifier_drag_handle_padding"
android:scaleType="center"
android:contentDescription="@string/magnification_drag_corner_to_resize"
- android:src="@drawable/ic_magnification_corner_bottom_left"
+ android:src="@drawable/ic_arrow_top_right_and_bottom_left"
android:accessibilityTraversalAfter="@id/bottom_right_corner"/>
<ImageView
@@ -124,7 +121,8 @@
android:layout_gravity="right|bottom"
android:padding="@dimen/magnifier_drag_handle_padding"
android:scaleType="center"
- android:src="@drawable/ic_move_magnification"/>
+ android:src="@drawable/ic_move_magnification"
+ android:background="@drawable/accessibility_window_magnification_drag_handle_background"/>
<ImageView
android:id="@+id/close_button"
diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index 375bd39..0ca154e 100644
--- a/packages/SystemUI/res/values-television/config.xml
+++ b/packages/SystemUI/res/values-television/config.xml
@@ -46,4 +46,7 @@
<!-- Whether to tint the icon of the sensor hardware privacy toggle unblock dialog.
Set to false if using a custom icon. -->
<bool name="config_unblockHwSensorIconEnableTint">true</bool>
+
+ <!-- Configuration to set Learn more in device logs as URL link -->
+ <bool name="log_access_confirmation_learn_more_as_link">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values-television/strings.xml b/packages/SystemUI/res/values-television/strings.xml
index f30b73e..86106e6 100644
--- a/packages/SystemUI/res/values-television/strings.xml
+++ b/packages/SystemUI/res/values-television/strings.xml
@@ -17,11 +17,6 @@
*/
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
- <string name="log_access_confirmation_body">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs.
- \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs.
- </string>
-
<!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
- <string name="log_access_confirmation_learn_more" translatable="false"></string>
+ <string name="log_access_confirmation_learn_more_url" translatable="false"></string>
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 5b6c9d3..b3256ef 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -219,5 +219,12 @@
<attr name="biometricsEnrollProgressHelp" format="reference|color" />
<attr name="biometricsEnrollProgressHelpWithTalkback" format="reference|color" />
</declare-styleable>
+
+ <declare-styleable name="SeekBarWithIconButtonsView_Layout">
+ <attr name="max" format="integer" />
+ <attr name="progress" format="integer" />
+ <attr name="iconStartContentDescription" format="reference" />
+ <attr name="iconEndContentDescription" format="reference" />
+ </declare-styleable>
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 5bb96c4..7360c79 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -168,10 +168,11 @@
<color name="GM2_yellow_200">#FDE293</color>
<!-- Window magnification colors -->
- <color name="magnification_border_color">#FF9900</color>
- <color name="magnification_border_color_change">#0000FF</color>
+ <color name="magnification_border_color">#F29900</color>
<color name="magnification_switch_button_color">#7F000000</color>
+ <color name="magnification_drag_corner_background">#E5FFFFFF</color>
<color name="magnification_drag_handle_color">#B3000000</color>
+ <color name="magnification_drag_handle_tint">#111111</color>
<color name="accessibility_magnifier_bg">#FCFCFC</color>
<color name="accessibility_magnifier_bg_stroke">#E0E0E0</color>
<color name="accessibility_magnifier_icon_color">#252525</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index f5db5ec..050b1e1 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -445,6 +445,13 @@
screenshot has been saved to work profile. If blank, a default icon will be shown. -->
<string name="config_sceenshotWorkProfileFilesApp" translatable="false"></string>
+ <!-- The component name of the screenshot editing activity that provides the App Clips flow.
+ The App Clips flow includes taking a screenshot, showing user screenshot cropping activity
+ and finally letting user send the screenshot to the calling notes app. This activity
+ should not send the screenshot to the calling activity without user consent. -->
+ <string name="config_screenshotAppClipsActivityComponent" translatable="false"
+ >com.android.systemui/com.android.systemui.screenshot.AppClipsActivity</string>
+
<!-- Remote copy default activity. Must handle REMOTE_COPY_ACTION intents.
This name is in the ComponentName flattened format (package/class) -->
<string name="config_remoteCopyPackage" translatable="false"></string>
@@ -835,4 +842,7 @@
<!-- Whether the floating rotation button should be on the left/right in the device's natural
orientation -->
<bool name="floating_rotation_button_position_left">true</bool>
+
+ <!-- Configuration to set Learn more in device logs as URL link -->
+ <bool name="log_access_confirmation_learn_more_as_link">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 80756f1..ebf232f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1099,6 +1099,7 @@
<dimen name="magnification_outer_border_margin">15dp</dimen>
<dimen name="magnification_inner_border_margin">17dp</dimen>
<dimen name="magnification_mirror_surface_margin">20dp</dimen>
+ <dimen name="magnification_drag_corner_margin">8dp</dimen>
<dimen name="magnification_frame_move_short">5dp</dimen>
<dimen name="magnification_frame_move_long">25dp</dimen>
<dimen name="magnification_drag_view_size">36dp</dimen>
@@ -1111,6 +1112,15 @@
<dimen name="magnifier_left_right_controls_height">45dp</dimen>
<dimen name="magnifier_up_down_controls_width">45dp</dimen>
<dimen name="magnifier_up_down_controls_height">40dp</dimen>
+ <dimen name="magnifier_outer_corner_radius">30dp</dimen>
+ <dimen name="magnifier_corner_radius">28dp</dimen>
+ <dimen name="magnifier_edit_corner_radius">16dp</dimen>
+ <dimen name="magnifier_edit_outer_corner_radius">18dp</dimen>
+ <dimen name="magnifier_border_width">8dp</dimen>
+ <dimen name="magnifier_stroke_width">2dp</dimen>
+ <dimen name="magnifier_edit_dash_gap">20dp</dimen>
+ <dimen name="magnification_window_drag_corner_size">26dp</dimen>
+ <dimen name="magnification_window_drag_corner_stroke">3dp</dimen>
<!-- The extra padding to show the whole outer border -->
<dimen name="magnifier_drag_handle_padding">3dp</dimen>
<dimen name="magnification_max_frame_size">300dp</dimen>
@@ -1134,6 +1144,8 @@
<dimen name="magnification_setting_image_button_padding_horizontal">24dp</dimen>
<dimen name="magnification_setting_image_button_open_in_full_padding_vertical">16dp</dimen>
<dimen name="magnification_setting_image_button_open_in_full_padding_horizontal">28dp</dimen>
+ <dimen name="magnification_setting_seekbar_icon_size">24dp</dimen>
+
<!-- How far from the right edge of the screen you need to drag the window before the button
repositions to the other side. -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 16bffb7..b535e60 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -240,13 +240,15 @@
<!-- Content description for the right boundary of the screenshot being cropped, with the current position as a percentage. [CHAR LIMIT=NONE] -->
<string name="screenshot_right_boundary_pct">Right boundary <xliff:g id="percent" example="50">%1$d</xliff:g> percent</string>
<!-- Notification displayed when a screenshot is saved in a work profile. [CHAR LIMIT=NONE] -->
- <string name="screenshot_work_profile_notification">Work screenshots are saved in the <xliff:g id="app" example="Work Files">%1$s</xliff:g> app</string>
+ <string name="screenshot_work_profile_notification">Saved in <xliff:g id="app" example="Files">%1$s</xliff:g> in the work profile</string>
<!-- Default name referring to the app on the device that lets the user browse stored files. [CHAR LIMIT=NONE] -->
<string name="screenshot_default_files_app_name">Files</string>
<!-- A notice shown to the user to indicate that an app has detected the screenshot that the user has just taken. [CHAR LIMIT=75] -->
<string name="screenshot_detected_template"><xliff:g id="appName" example="Google Chrome">%1$s</xliff:g> detected this screenshot.</string>
<!-- A notice shown to the user to indicate that multiple apps have detected the screenshot that the user has just taken. [CHAR LIMIT=75] -->
<string name="screenshot_detected_multiple_template"><xliff:g id="appName" example="Google Chrome">%1$s</xliff:g> and other open apps detected this screenshot.</string>
+ <!-- Add to note button used in App Clips flow to return the saved screenshot image to notes app. [CHAR LIMIT=NONE] -->
+ <string name="app_clips_save_add_to_note">Add to note</string>
<!-- Notification title displayed for screen recording [CHAR LIMIT=50]-->
<string name="screenrecord_name">Screen Recorder</string>
@@ -2538,8 +2540,7 @@
<string name="media_output_group_title_speakers_and_displays">Speakers & Displays</string>
<!-- Title for Suggested Devices group. [CHAR LIMIT=NONE] -->
<string name="media_output_group_title_suggested_device">Suggested Devices</string>
- <!-- Sub status indicates device need premium account. [CHAR LIMIT=NONE] -->
- <string name="media_output_status_require_premium">Requires premium account</string>
+
<!-- Media Output Broadcast Dialog -->
<!-- Title for Broadcast First Notify Dialog [CHAR LIMIT=60] -->
@@ -2798,6 +2799,8 @@
<string name="dream_overlay_status_bar_mic_off">Mic is off</string>
<!-- Content description for the camera and mic off icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
<string name="dream_overlay_status_bar_camera_mic_off">Camera and mic are off</string>
+ <!-- Content description for the assistant attention indicator [CHAR LIMIT=NONE] -->
+ <string name="dream_overlay_status_bar_assistant_attention_indicator">Assistant is listening</string>
<!-- Content description for the notifications indicator icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
<string name="dream_overlay_status_bar_notification_indicator">{count, plural,
=1 {# notification}
@@ -2844,7 +2847,9 @@
</string>
<!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
- <string name="log_access_confirmation_learn_more" translatable="false"><a href="https://support.google.com/android?p=system_logs#topic=7313011">Learn more</a></string>
+ <string name="log_access_confirmation_learn_more_url" translatable="false">https://support.google.com/android?p=system_logs#topic=7313011</string>
+ <string name="log_access_confirmation_learn_more">Learn more</string>
+ <string name="log_access_confirmation_learn_more_at">Learn more at <xliff:g id="url" example="g.co/android/devicelogs">%s</xliff:g></string>
<!--
Template for an action that opens a specific app. [CHAR LIMIT=16]
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index dd87e91..4998d68 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -758,6 +758,18 @@
</style>
<!-- Screenshots -->
+ <style name="AppClipsTrampolineActivity">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowIsFloating">true</item>
+ <item name="android:backgroundDimEnabled">true</item>
+ </style>
+
+ <style name="AppClipsActivity" parent="LongScreenshotActivity">
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowIsTranslucent">true</item>
+ </style>
+
<style name="LongScreenshotActivity" parent="@android:style/Theme.DeviceDefault.DayNight">
<item name="android:windowNoTitle">true</item>
<item name="android:windowLightStatusBar">true</item>
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 3a940e9..7f95ca6 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -25,6 +25,7 @@
import android.util.TypedValue
import android.view.View
import android.widget.FrameLayout
+import android.view.ViewTreeObserver
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
@@ -42,12 +43,15 @@
import com.android.systemui.log.dagger.KeyguardSmallClockLog
import com.android.systemui.log.dagger.KeyguardLargeClockLog
import com.android.systemui.plugins.ClockController
+import com.android.systemui.plugins.ClockFaceController
+import com.android.systemui.plugins.ClockTickRate
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel.DEBUG
import com.android.systemui.shared.regionsampling.RegionSampler
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
@@ -72,7 +76,7 @@
private val configurationController: ConfigurationController,
@Main private val resources: Resources,
private val context: Context,
- @Main private val mainExecutor: Executor,
+ @Main private val mainExecutor: DelayableExecutor,
@Background private val bgExecutor: Executor,
@KeyguardSmallClockLog private val smallLogBuffer: LogBuffer?,
@KeyguardLargeClockLog private val largeLogBuffer: LogBuffer?,
@@ -92,8 +96,11 @@
if (regionSamplingEnabled) {
clock?.smallClock?.view?.addOnLayoutChangeListener(mLayoutChangedListener)
clock?.largeClock?.view?.addOnLayoutChangeListener(mLayoutChangedListener)
+ } else {
+ updateColors()
}
updateFontSizes()
+ updateTimeListeners()
}
}
@@ -208,6 +215,10 @@
}
var regionSampler: RegionSampler? = null
+ var smallTimeListener: TimeListener? = null
+ var largeTimeListener: TimeListener? = null
+ val shouldTimeListenerRun: Boolean
+ get() = isKeyguardVisible && dozeAmount < DOZE_TICKRATE_THRESHOLD
private var smallClockIsDark = true
private var largeClockIsDark = true
@@ -246,6 +257,9 @@
clock?.animations?.doze(if (isDozing) 1f else 0f)
}
}
+
+ smallTimeListener?.update(shouldTimeListenerRun)
+ largeTimeListener?.update(shouldTimeListenerRun)
}
override fun onTimeFormatChanged(timeFormat: String) {
@@ -285,6 +299,8 @@
}
}
}
+ smallTimeListener?.update(shouldTimeListenerRun)
+ largeTimeListener?.update(shouldTimeListenerRun)
}
fun unregisterListeners() {
@@ -299,6 +315,25 @@
batteryController.removeCallback(batteryCallback)
keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
regionSampler?.stopRegionSampler()
+ smallTimeListener?.stop()
+ largeTimeListener?.stop()
+ }
+
+ private fun updateTimeListeners() {
+ smallTimeListener?.stop()
+ largeTimeListener?.stop()
+
+ smallTimeListener = null
+ largeTimeListener = null
+
+ clock?.smallClock?.let {
+ smallTimeListener = TimeListener(it, mainExecutor)
+ smallTimeListener?.update(shouldTimeListenerRun)
+ }
+ clock?.largeClock?.let {
+ largeTimeListener = TimeListener(it, mainExecutor)
+ largeTimeListener?.update(shouldTimeListenerRun)
+ }
}
private fun updateFontSizes() {
@@ -308,12 +343,18 @@
resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat())
}
+ private fun handleDoze(doze: Float) {
+ dozeAmount = doze
+ clock?.animations?.doze(dozeAmount)
+ smallTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD)
+ largeTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD)
+ }
+
@VisibleForTesting
internal fun listenForDozeAmount(scope: CoroutineScope): Job {
return scope.launch {
keyguardInteractor.dozeAmount.collect {
- dozeAmount = it
- clock?.animations?.doze(dozeAmount)
+ handleDoze(it)
}
}
}
@@ -322,8 +363,7 @@
internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
return scope.launch {
keyguardTransitionInteractor.dozeAmountTransition.collect {
- dozeAmount = it.value
- clock?.animations?.doze(dozeAmount)
+ handleDoze(it.value)
}
}
}
@@ -338,8 +378,7 @@
keyguardTransitionInteractor.anyStateToAodTransition.filter {
it.transitionState == TransitionState.FINISHED
}.collect {
- dozeAmount = 1f
- clock?.animations?.doze(dozeAmount)
+ handleDoze(1f)
}
}
}
@@ -359,7 +398,54 @@
}
}
+ class TimeListener(val clockFace: ClockFaceController, val executor: DelayableExecutor) {
+ val predrawListener = ViewTreeObserver.OnPreDrawListener {
+ clockFace.events.onTimeTick()
+ true
+ }
+
+ val secondsRunnable = object : Runnable {
+ override fun run() {
+ if (!isRunning) {
+ return
+ }
+
+ executor.executeDelayed(this, 990)
+ clockFace.events.onTimeTick()
+ }
+ }
+
+ var isRunning: Boolean = false
+ private set
+
+ fun start() {
+ if (isRunning) {
+ return
+ }
+
+ isRunning = true
+ when (clockFace.events.tickRate) {
+ ClockTickRate.PER_MINUTE -> {/* Handled by KeyguardClockSwitchController */}
+ ClockTickRate.PER_SECOND -> executor.execute(secondsRunnable)
+ ClockTickRate.PER_FRAME -> {
+ clockFace.view.viewTreeObserver.addOnPreDrawListener(predrawListener)
+ clockFace.view.invalidate()
+ }
+ }
+ }
+
+ fun stop() {
+ if (!isRunning) { return }
+
+ isRunning = false
+ clockFace.view.viewTreeObserver.removeOnPreDrawListener(predrawListener)
+ }
+
+ fun update(shouldRun: Boolean) = if (shouldRun) start() else stop()
+ }
+
companion object {
private val TAG = ClockEventController::class.simpleName!!
+ private val DOZE_TICKRATE_THRESHOLD = 0.99f
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 0685794..3eec565 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -91,6 +91,7 @@
private ViewGroup mStatusArea;
// If the SMARTSPACE flag is set, keyguard_slice_view is replaced by the following views.
+ private ViewGroup mDateWeatherView;
private View mWeatherView;
private View mSmartspaceView;
@@ -201,7 +202,7 @@
// TODO(b/261757708): add content observer for the Settings toggle and add/remove
// weather according to the Settings.
if (mSmartspaceController.isDateWeatherDecoupled()) {
- addWeatherView(viewIndex);
+ addDateWeatherView(viewIndex);
viewIndex += 1;
}
@@ -239,6 +240,14 @@
void onLocaleListChanged() {
if (mSmartspaceController.isEnabled()) {
+ if (mSmartspaceController.isDateWeatherDecoupled()) {
+ mDateWeatherView.removeView(mWeatherView);
+ int index = mStatusArea.indexOfChild(mDateWeatherView);
+ if (index >= 0) {
+ mStatusArea.removeView(mDateWeatherView);
+ addDateWeatherView(index);
+ }
+ }
int index = mStatusArea.indexOfChild(mSmartspaceView);
if (index >= 0) {
mStatusArea.removeView(mSmartspaceView);
@@ -247,16 +256,28 @@
}
}
- private void addWeatherView(int index) {
- mWeatherView = mSmartspaceController.buildAndConnectWeatherView(mView);
+ private void addDateWeatherView(int index) {
+ mDateWeatherView = (ViewGroup) mSmartspaceController.buildAndConnectDateView(mView);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
MATCH_PARENT, WRAP_CONTENT);
- mStatusArea.addView(mWeatherView, index, lp);
+ mStatusArea.addView(mDateWeatherView, index, lp);
int startPadding = getContext().getResources().getDimensionPixelSize(
R.dimen.below_clock_padding_start);
int endPadding = getContext().getResources().getDimensionPixelSize(
R.dimen.below_clock_padding_end);
- mWeatherView.setPaddingRelative(startPadding, 0, endPadding, 0);
+ mDateWeatherView.setPaddingRelative(startPadding, 0, endPadding, 0);
+
+ addWeatherView();
+ }
+
+ private void addWeatherView() {
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+ WRAP_CONTENT, WRAP_CONTENT);
+ mWeatherView = mSmartspaceController.buildAndConnectWeatherView(mView);
+ // Place weather right after the date, before the extras
+ final int index = mDateWeatherView.getChildCount() == 0 ? 0 : 1;
+ mDateWeatherView.addView(mWeatherView, index, lp);
+ mWeatherView.setPaddingRelative(0, 0, 4, 0);
}
private void addSmartspaceView(int index) {
@@ -323,7 +344,8 @@
}
ClockController clock = getClock();
if (clock != null) {
- clock.getEvents().onTimeTick();
+ clock.getSmallClock().getEvents().onTimeTick();
+ clock.getLargeClock().getEvents().onTimeTick();
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
index 08e9cf6..2a389b6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.widget.FrameLayout;
/**
@@ -33,7 +34,7 @@
public class KeyguardHostView extends FrameLayout {
protected ViewMediatorCallback mViewMediatorCallback;
-
+ private boolean mIsInteractable;
public KeyguardHostView(Context context) {
this(context, null);
@@ -54,4 +55,24 @@
public void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) {
mViewMediatorCallback = viewMediatorCallback;
}
+
+ /** Set true if the view can be interacted with */
+ public void setInteractable(boolean isInteractable) {
+ mIsInteractable = isInteractable;
+ }
+
+ /**
+ * Make sure to disallow touches while transitioning the bouncer, otherwise
+ * it can remain interactable even when barely visible.
+ */
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ return !mIsInteractable;
+ }
+
+ /** True to consume any events that are sent to it */
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ return true;
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index ea84438..6139403 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -527,4 +527,9 @@
mKeyguardSecurityContainerController.updateKeyguardPosition(x);
}
}
+
+ /** Set true if the view can be interacted with */
+ public void setInteractable(boolean isInteractable) {
+ mView.setInteractable(isInteractable);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index 3e0fa45..54939fd 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -35,6 +35,7 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.Utils
import com.android.systemui.animation.Interpolators
+import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import java.util.concurrent.Executor
@@ -47,7 +48,8 @@
pos: Int,
val statusBarStateController: StatusBarStateController,
val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- val mainExecutor: Executor
+ val mainExecutor: Executor,
+ val logger: ScreenDecorationsLogger,
) : ScreenDecorations.DisplayCutoutView(context, pos) {
private var showScanningAnim = false
private val rimPaint = Paint()
@@ -55,6 +57,7 @@
private var rimAnimator: AnimatorSet? = null
private val rimRect = RectF()
private var cameraProtectionColor = Color.BLACK
+
var faceScanningAnimColor = Utils.getColorAttrDefaultColor(context,
R.attr.wallpaperTextColorAccent)
private var cameraProtectionAnimator: ValueAnimator? = null
@@ -175,15 +178,22 @@
}
if (showScanningAnim) {
// Make sure that our measured height encompasses the extra space for the animation
- mTotalBounds.union(mBoundingRect)
+ mTotalBounds.set(mBoundingRect)
mTotalBounds.union(
rimRect.left.toInt(),
rimRect.top.toInt(),
rimRect.right.toInt(),
rimRect.bottom.toInt())
- setMeasuredDimension(
- resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0),
- resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0))
+ val measuredWidth = resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0)
+ val measuredHeight = resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0)
+ logger.boundingRect(rimRect, "onMeasure: Face scanning animation")
+ logger.boundingRect(mBoundingRect, "onMeasure: Display cutout view bounding rect")
+ logger.boundingRect(mTotalBounds, "onMeasure: TotalBounds")
+ logger.onMeasureDimensions(widthMeasureSpec,
+ heightMeasureSpec,
+ measuredWidth,
+ measuredHeight)
+ setMeasuredDimension(measuredWidth, measuredHeight)
} else {
setMeasuredDimension(
resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 71f98fa..c1c7f2d 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -64,6 +64,7 @@
import com.android.internal.util.Preconditions;
import com.android.settingslib.Utils;
+import com.android.systemui.biometrics.AuthController;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.decor.CutoutDecorProviderFactory;
@@ -75,6 +76,7 @@
import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
import com.android.systemui.decor.RoundedCornerDecorProviderFactory;
import com.android.systemui.decor.RoundedCornerResDelegate;
+import com.android.systemui.log.ScreenDecorationsLogger;
import com.android.systemui.qs.SettingObserver;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
@@ -120,6 +122,18 @@
R.id.display_cutout_right,
R.id.display_cutout_bottom
};
+ private final ScreenDecorationsLogger mLogger;
+
+ private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
+ @Override
+ public void onFaceSensorLocationChanged() {
+ mLogger.onSensorLocationChanged();
+ if (mExecutor != null) {
+ mExecutor.execute(
+ () -> updateOverlayProviderViews(new Integer[]{mFaceScanningViewId}));
+ }
+ }
+ };
private DisplayTracker mDisplayTracker;
@VisibleForTesting
@@ -153,6 +167,7 @@
private WindowManager mWindowManager;
private int mRotation;
private SettingObserver mColorInversionSetting;
+ @Nullable
private DelayableExecutor mExecutor;
private Handler mHandler;
boolean mPendingConfigChange;
@@ -172,6 +187,7 @@
DisplayCutoutView overlay = (DisplayCutoutView) getOverlayView(
mFaceScanningViewId);
if (overlay != null) {
+ mLogger.cameraProtectionBoundsForScanningOverlay(bounds);
overlay.setProtection(protectionPath, bounds);
overlay.enableShowProtection(true);
updateOverlayWindowVisibilityIfViewExists(
@@ -184,6 +200,7 @@
}
if (mScreenDecorHwcLayer != null) {
+ mLogger.hwcLayerCameraProtectionBounds(bounds);
mScreenDecorHwcLayer.setProtection(protectionPath, bounds);
mScreenDecorHwcLayer.enableShowProtection(true);
return;
@@ -197,11 +214,12 @@
}
++setProtectionCnt;
final DisplayCutoutView dcv = (DisplayCutoutView) view;
+ mLogger.dcvCameraBounds(id, bounds);
dcv.setProtection(protectionPath, bounds);
dcv.enableShowProtection(true);
}
if (setProtectionCnt == 0) {
- Log.e(TAG, "CutoutView not initialized showCameraProtection");
+ mLogger.cutoutViewNotInitialized();
}
}
@@ -307,7 +325,9 @@
PrivacyDotViewController dotViewController,
ThreadFactory threadFactory,
PrivacyDotDecorProviderFactory dotFactory,
- FaceScanningProviderFactory faceScanningFactory) {
+ FaceScanningProviderFactory faceScanningFactory,
+ ScreenDecorationsLogger logger,
+ AuthController authController) {
mContext = context;
mMainExecutor = mainExecutor;
mSecureSettings = secureSettings;
@@ -319,6 +339,8 @@
mDotFactory = dotFactory;
mFaceScanningFactory = faceScanningFactory;
mFaceScanningViewId = com.android.systemui.R.id.face_scanning_anim;
+ mLogger = logger;
+ authController.addCallback(mAuthControllerCallback);
}
@Override
@@ -1306,7 +1328,7 @@
if (showProtection) {
// Make sure that our measured height encompasses the protection
- mTotalBounds.union(mBoundingRect);
+ mTotalBounds.set(mBoundingRect);
mTotalBounds.union((int) protectionRect.left, (int) protectionRect.top,
(int) protectionRect.right, (int) protectionRect.bottom);
setMeasuredDimension(
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index ffdd861..8578845 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -38,6 +38,7 @@
import android.util.TimingsTraceLog;
import android.view.SurfaceControl;
import android.view.ThreadedRenderer;
+import android.view.View;
import com.android.internal.protolog.common.ProtoLog;
import com.android.systemui.dagger.GlobalRootComponent;
@@ -112,6 +113,11 @@
// the theme set there.
setTheme(R.style.Theme_SystemUI);
+ View.setTraceLayoutSteps(
+ SystemProperties.getBoolean("persist.debug.trace_layouts", false));
+ View.setTracedRequestLayoutClassClass(
+ SystemProperties.get("persist.debug.trace_request_layout_class", null));
+
if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
IntentFilter bootCompletedFilter = new
IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 0a0a36b..b111e1f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -49,6 +49,7 @@
import android.util.Log;
import android.util.Range;
import android.util.Size;
+import android.util.TypedValue;
import android.view.Choreographer;
import android.view.Display;
import android.view.Gravity;
@@ -1273,9 +1274,19 @@
}
private void applyResourcesValues() {
- mMirrorBorderView.setBackgroundColor(mResources.getColor(mEditSizeEnable
- ? R.color.magnification_border_color_change : R.color.magnification_border_color));
+ // Sets the border appearance for the magnifier window
+ mMirrorBorderView.setBackground(mResources.getDrawable(mEditSizeEnable
+ ? R.drawable.accessibility_window_magnification_background_change
+ : R.drawable.accessibility_window_magnification_background));
+ // Changes the corner radius of the mMirrorSurfaceView
+ mMirrorSurfaceView.setCornerRadius(
+ TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ mEditSizeEnable ? 16f : 28f,
+ mContext.getResources().getDisplayMetrics()));
+
+ // Sets visibility of components for the magnifier window
if (mEditSizeEnable) {
mDragView.setVisibility(View.GONE);
mCloseView.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 80110c7..4c1a9fa 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -53,6 +53,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.R;
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView;
import com.android.systemui.util.settings.SecureSettings;
import java.lang.annotation.Retention;
@@ -79,7 +80,7 @@
private final MagnificationGestureDetector mGestureDetector;
private boolean mSingleTapDetected = false;
- private SeekBar mZoomSeekbar;
+ private SeekBarWithIconButtonsView mZoomSeekbar;
private Switch mAllowDiagonalScrollingSwitch;
private LinearLayout mPanelView;
private LinearLayout mSettingView;
@@ -393,7 +394,8 @@
mEditButton = mSettingView.findViewById(R.id.magnifier_edit_button);
mChangeModeButton = mSettingView.findViewById(R.id.magnifier_full_button);
- mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_seekbar);
+ mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider);
+
mZoomSeekbar.setOnSeekBarChangeListener(new ZoomSeekbarChangeListener());
float scale = mSecureSettings.getFloatForUser(
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
deleted file mode 100644
index 81d7766..0000000
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED;
-import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_ICON_TYPE;
-import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT;
-import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY;
-import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
-
-import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
-import static com.android.systemui.Prefs.Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP;
-import static com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.ShapeType;
-import static com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.SizeType;
-
-import android.annotation.FloatRange;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.accessibility.dialog.AccessibilityTarget;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Prefs;
-import com.android.systemui.shared.system.SysUiStatsLog;
-import com.android.systemui.util.settings.SecureSettings;
-
-import java.util.List;
-
-/**
- * Contains logic for an accessibility floating menu view.
- */
-public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
- private static final int DEFAULT_FADE_EFFECT_IS_ENABLED = 1;
- private static final int DEFAULT_MIGRATION_TOOLTIP_PROMPT_IS_DISABLED = 0;
- @FloatRange(from = 0.0, to = 1.0)
- private static final float DEFAULT_OPACITY_VALUE = 0.55f;
- @FloatRange(from = 0.0, to = 1.0)
- private static final float DEFAULT_POSITION_X_PERCENT = 1.0f;
- @FloatRange(from = 0.0, to = 1.0)
- private static final float DEFAULT_POSITION_Y_PERCENT = 0.77f;
-
- private final Context mContext;
- private final SecureSettings mSecureSettings;
- private final AccessibilityFloatingMenuView mMenuView;
- private final MigrationTooltipView mMigrationTooltipView;
- private final DockTooltipView mDockTooltipView;
- private final Handler mHandler = new Handler(Looper.getMainLooper());
-
- private final ContentObserver mContentObserver =
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- mMenuView.onTargetsChanged(getTargets(mContext, ACCESSIBILITY_BUTTON));
- }
- };
-
- private final ContentObserver mSizeContentObserver =
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- mMenuView.setSizeType(getSizeType());
- }
- };
-
- private final ContentObserver mFadeOutContentObserver =
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- mMenuView.updateOpacityWith(isFadeEffectEnabled(),
- getOpacityValue());
- }
- };
-
- private final ContentObserver mEnabledA11yServicesContentObserver =
- new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- mMenuView.onEnabledFeaturesChanged();
- }
- };
-
- public AccessibilityFloatingMenu(Context context, SecureSettings secureSettings) {
- mContext = context;
- mSecureSettings = secureSettings;
- mMenuView = new AccessibilityFloatingMenuView(context, getPosition(context));
- mMigrationTooltipView = new MigrationTooltipView(mContext, mMenuView);
- mDockTooltipView = new DockTooltipView(mContext, mMenuView);
- }
-
- @VisibleForTesting
- AccessibilityFloatingMenu(Context context, SecureSettings secureSettings,
- AccessibilityFloatingMenuView menuView) {
- mContext = context;
- mSecureSettings = secureSettings;
- mMenuView = menuView;
- mMigrationTooltipView = new MigrationTooltipView(mContext, mMenuView);
- mDockTooltipView = new DockTooltipView(mContext, mMenuView);
- }
-
- @Override
- public boolean isShowing() {
- return mMenuView.isShowing();
- }
-
- @Override
- public void show() {
- if (isShowing()) {
- return;
- }
- final List<AccessibilityTarget> targetList = getTargets(mContext, ACCESSIBILITY_BUTTON);
- if (targetList.isEmpty()) {
- return;
- }
-
- mMenuView.show();
- mMenuView.onTargetsChanged(targetList);
- mMenuView.updateOpacityWith(isFadeEffectEnabled(),
- getOpacityValue());
- mMenuView.setSizeType(getSizeType());
- mMenuView.setShapeType(getShapeType());
- mMenuView.setOnDragEndListener(this::onDragEnd);
-
- showMigrationTooltipIfNecessary();
-
- registerContentObservers();
- }
-
- @Override
- public void hide() {
- if (!isShowing()) {
- return;
- }
-
- mMenuView.hide();
- mMenuView.setOnDragEndListener(null);
- mMigrationTooltipView.hide();
- mDockTooltipView.hide();
-
- unregisterContentObservers();
- }
-
- @NonNull
- private Position getPosition(Context context) {
- final String absolutePositionString = Prefs.getString(context,
- Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null);
-
- if (TextUtils.isEmpty(absolutePositionString)) {
- return new Position(DEFAULT_POSITION_X_PERCENT, DEFAULT_POSITION_Y_PERCENT);
- } else {
- return Position.fromString(absolutePositionString);
- }
- }
-
- // Migration tooltip was the android S feature. It's just used on the Android version from R
- // to S. In addition, it only shows once.
- private void showMigrationTooltipIfNecessary() {
- if (isMigrationTooltipPromptEnabled()) {
- mMigrationTooltipView.show();
-
- mSecureSettings.putInt(
- ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT, /* disabled */ 0);
- }
- }
-
- private boolean isMigrationTooltipPromptEnabled() {
- return mSecureSettings.getInt(
- ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
- DEFAULT_MIGRATION_TOOLTIP_PROMPT_IS_DISABLED) == /* enabled */ 1;
- }
-
- private void onDragEnd(Position position) {
- SysUiStatsLog.write(SysUiStatsLog.ACCESSIBILITY_FLOATING_MENU_UI_CHANGED,
- position.getPercentageX(), position.getPercentageY(),
- mContext.getResources().getConfiguration().orientation);
- savePosition(mContext, position);
- showDockTooltipIfNecessary(mContext);
- }
-
- private void savePosition(Context context, Position position) {
- Prefs.putString(context, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION,
- position.toString());
- }
-
- /**
- * Shows tooltip when user drags accessibility floating menu for the first time.
- */
- private void showDockTooltipIfNecessary(Context context) {
- if (!Prefs.get(context).getBoolean(
- HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP, false)) {
- // if the menu is an oval, the user has already dragged it out, so show the tooltip.
- if (mMenuView.isOvalShape()) {
- mDockTooltipView.show();
- }
-
- Prefs.putBoolean(context, HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP, true);
- }
- }
-
- private boolean isFadeEffectEnabled() {
- return mSecureSettings.getInt(
- ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
- DEFAULT_FADE_EFFECT_IS_ENABLED) == /* enabled */ 1;
- }
-
- private float getOpacityValue() {
- return mSecureSettings.getFloat(
- ACCESSIBILITY_FLOATING_MENU_OPACITY,
- DEFAULT_OPACITY_VALUE);
- }
-
- private int getSizeType() {
- return mSecureSettings.getInt(
- ACCESSIBILITY_FLOATING_MENU_SIZE, SizeType.SMALL);
- }
-
- private int getShapeType() {
- return mSecureSettings.getInt(
- ACCESSIBILITY_FLOATING_MENU_ICON_TYPE,
- ShapeType.OVAL);
- }
-
- private void registerContentObservers() {
- mSecureSettings.registerContentObserverForUser(
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
- /* notifyForDescendants */ false, mContentObserver,
- UserHandle.USER_CURRENT);
- mSecureSettings.registerContentObserverForUser(
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
- /* notifyForDescendants */ false, mContentObserver,
- UserHandle.USER_CURRENT);
- mSecureSettings.registerContentObserverForUser(
- Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
- /* notifyForDescendants */ false, mSizeContentObserver,
- UserHandle.USER_CURRENT);
- mSecureSettings.registerContentObserverForUser(
- Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
- /* notifyForDescendants */ false, mFadeOutContentObserver,
- UserHandle.USER_CURRENT);
- mSecureSettings.registerContentObserverForUser(
- Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY,
- /* notifyForDescendants */ false, mFadeOutContentObserver,
- UserHandle.USER_CURRENT);
- mSecureSettings.registerContentObserverForUser(
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
- /* notifyForDescendants */ false,
- mEnabledA11yServicesContentObserver, UserHandle.USER_CURRENT);
- }
-
- private void unregisterContentObservers() {
- mSecureSettings.unregisterContentObserver(mContentObserver);
- mSecureSettings.unregisterContentObserver(mSizeContentObserver);
- mSecureSettings.unregisterContentObserver(mFadeOutContentObserver);
- mSecureSettings.unregisterContentObserver(
- mEnabledA11yServicesContentObserver);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index 2c413a2..5924149 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -19,8 +19,6 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
-import static com.android.systemui.flags.Flags.A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS;
-
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.os.UserHandle;
@@ -38,7 +36,6 @@
import com.android.systemui.accessibility.AccessibilityButtonModeObserver.AccessibilityButtonMode;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.util.settings.SecureSettings;
@@ -59,7 +56,7 @@
private final WindowManager mWindowManager;
private final DisplayManager mDisplayManager;
private final AccessibilityManager mAccessibilityManager;
- private final FeatureFlags mFeatureFlags;
+
private final SecureSettings mSecureSettings;
private final DisplayTracker mDisplayTracker;
@VisibleForTesting
@@ -105,7 +102,6 @@
AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver,
AccessibilityButtonModeObserver accessibilityButtonModeObserver,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- FeatureFlags featureFlags,
SecureSettings secureSettings,
DisplayTracker displayTracker) {
mContext = context;
@@ -115,7 +111,6 @@
mAccessibilityButtonTargetsObserver = accessibilityButtonTargetsObserver;
mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mFeatureFlags = featureFlags;
mSecureSettings = secureSettings;
mDisplayTracker = displayTracker;
@@ -162,7 +157,7 @@
* Handles the accessibility floating menu visibility with the given values.
*
* @param keyguardVisible the keyguard visibility status. Not show the
- * {@link AccessibilityFloatingMenu} when keyguard appears.
+ * {@link MenuView} when keyguard appears.
* @param mode accessibility button mode {@link AccessibilityButtonMode}
* @param targets accessibility button list; it should comes from
* {@link android.provider.Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS}.
@@ -187,16 +182,12 @@
private void showFloatingMenu() {
if (mFloatingMenu == null) {
- if (mFeatureFlags.isEnabled(A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS)) {
- final Display defaultDisplay = mDisplayManager.getDisplay(
- mDisplayTracker.getDefaultDisplayId());
- final Context windowContext = mContext.createWindowContext(defaultDisplay,
- TYPE_NAVIGATION_BAR_PANEL, /* options= */ null);
- mFloatingMenu = new MenuViewLayerController(windowContext, mWindowManager,
- mAccessibilityManager, mSecureSettings);
- } else {
- mFloatingMenu = new AccessibilityFloatingMenu(mContext, mSecureSettings);
- }
+ final Display defaultDisplay = mDisplayManager.getDisplay(
+ mDisplayTracker.getDefaultDisplayId());
+ final Context windowContext = mContext.createWindowContext(defaultDisplay,
+ TYPE_NAVIGATION_BAR_PANEL, /* options= */ null);
+ mFloatingMenu = new MenuViewLayerController(windowContext, mWindowManager,
+ mAccessibilityManager, mSecureSettings);
}
mFloatingMenu.show();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
deleted file mode 100644
index aff0b1f..0000000
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
+++ /dev/null
@@ -1,921 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.util.MathUtils.constrain;
-import static android.util.MathUtils.sq;
-import static android.view.WindowInsets.Type.displayCutout;
-import static android.view.WindowInsets.Type.ime;
-import static android.view.WindowInsets.Type.systemBars;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
-
-import static java.util.Objects.requireNonNull;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.annotation.FloatRange;
-import android.annotation.IntDef;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Insets;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.LayerDrawable;
-import android.os.Handler;
-import android.os.Looper;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-import android.view.animation.Animation;
-import android.view.animation.OvershootInterpolator;
-import android.view.animation.TranslateAnimation;
-import android.widget.FrameLayout;
-
-import androidx.annotation.DimenRes;
-import androidx.annotation.NonNull;
-import androidx.core.view.AccessibilityDelegateCompat;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
-
-import com.android.internal.accessibility.dialog.AccessibilityTarget;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-
-/**
- * Accessibility floating menu is used for the actions of accessibility features, it's also the
- * action set.
- *
- * <p>The number of items would depend on strings key
- * {@link android.provider.Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS}.
- */
-public class AccessibilityFloatingMenuView extends FrameLayout
- implements RecyclerView.OnItemTouchListener {
- private static final int INDEX_MENU_ITEM = 0;
- private static final int FADE_OUT_DURATION_MS = 1000;
- private static final int FADE_EFFECT_DURATION_MS = 3000;
- private static final int SNAP_TO_LOCATION_DURATION_MS = 150;
- private static final int MIN_WINDOW_Y = 0;
-
- private static final int ANIMATION_START_OFFSET = 600;
- private static final int ANIMATION_DURATION_MS = 600;
- private static final float ANIMATION_TO_X_VALUE = 0.5f;
-
- private boolean mIsFadeEffectEnabled;
- private boolean mIsShowing;
- private boolean mIsDownInEnlargedTouchArea;
- private boolean mIsDragging = false;
- @Alignment
- private int mAlignment;
- @SizeType
- private int mSizeType = SizeType.SMALL;
- @VisibleForTesting
- @ShapeType
- int mShapeType = ShapeType.OVAL;
- private int mTemporaryShapeType;
- @RadiusType
- private int mRadiusType;
- private int mMargin;
- private int mPadding;
- // The display width excludes the window insets of the system bar and display cutout.
- private int mDisplayHeight;
- // The display Height excludes the window insets of the system bar and display cutout.
- private int mDisplayWidth;
- private int mIconWidth;
- private int mIconHeight;
- private int mInset;
- private int mDownX;
- private int mDownY;
- private int mRelativeToPointerDownX;
- private int mRelativeToPointerDownY;
- private float mRadius;
- private final Rect mDisplayInsetsRect = new Rect();
- private final Rect mImeInsetsRect = new Rect();
- private final Position mPosition;
- private float mSquareScaledTouchSlop;
- private final Configuration mLastConfiguration;
- private Optional<OnDragEndListener> mOnDragEndListener = Optional.empty();
- private final RecyclerView mListView;
- private final AccessibilityTargetAdapter mAdapter;
- private float mFadeOutValue;
- private final ValueAnimator mFadeOutAnimator;
- @VisibleForTesting
- final ValueAnimator mDragAnimator;
- private final Handler mUiHandler;
- @VisibleForTesting
- final WindowManager.LayoutParams mCurrentLayoutParams;
- private final WindowManager mWindowManager;
- private final List<AccessibilityTarget> mTargets = new ArrayList<>();
-
- @IntDef({
- SizeType.SMALL,
- SizeType.LARGE
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface SizeType {
- int SMALL = 0;
- int LARGE = 1;
- }
-
- @IntDef({
- ShapeType.OVAL,
- ShapeType.HALF_OVAL
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface ShapeType {
- int OVAL = 0;
- int HALF_OVAL = 1;
- }
-
- @IntDef({
- RadiusType.LEFT_HALF_OVAL,
- RadiusType.OVAL,
- RadiusType.RIGHT_HALF_OVAL
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface RadiusType {
- int LEFT_HALF_OVAL = 0;
- int OVAL = 1;
- int RIGHT_HALF_OVAL = 2;
- }
-
- @IntDef({
- Alignment.LEFT,
- Alignment.RIGHT
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface Alignment {
- int LEFT = 0;
- int RIGHT = 1;
- }
-
- /**
- * Interface for a callback to be invoked when the floating menu was dragging.
- */
- interface OnDragEndListener {
-
- /**
- * Called when a drag is completed.
- *
- * @param position Stores information about the position
- */
- void onDragEnd(Position position);
- }
-
- public AccessibilityFloatingMenuView(Context context, @NonNull Position position) {
- this(context, position, new RecyclerView(context));
- }
-
- @VisibleForTesting
- AccessibilityFloatingMenuView(Context context, @NonNull Position position,
- RecyclerView listView) {
- super(context);
-
- mListView = listView;
- mWindowManager = context.getSystemService(WindowManager.class);
- mLastConfiguration = new Configuration(getResources().getConfiguration());
- mAdapter = new AccessibilityTargetAdapter(mTargets);
- mUiHandler = createUiHandler();
- mPosition = position;
- mAlignment = transformToAlignment(mPosition.getPercentageX());
- mRadiusType = (mAlignment == Alignment.RIGHT)
- ? RadiusType.LEFT_HALF_OVAL
- : RadiusType.RIGHT_HALF_OVAL;
-
- updateDimensions();
-
- mCurrentLayoutParams = createDefaultLayoutParams();
-
- mFadeOutAnimator = ValueAnimator.ofFloat(1.0f, mFadeOutValue);
- mFadeOutAnimator.setDuration(FADE_OUT_DURATION_MS);
- mFadeOutAnimator.addUpdateListener(
- (animation) -> setAlpha((float) animation.getAnimatedValue()));
-
- mDragAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
- mDragAnimator.setDuration(SNAP_TO_LOCATION_DURATION_MS);
- mDragAnimator.setInterpolator(new OvershootInterpolator());
- mDragAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mPosition.update(transformCurrentPercentageXToEdge(),
- calculateCurrentPercentageY());
- mAlignment = transformToAlignment(mPosition.getPercentageX());
-
- updateLocationWith(mPosition);
-
- updateInsetWith(getResources().getConfiguration().uiMode, mAlignment);
-
- mRadiusType = (mAlignment == Alignment.RIGHT)
- ? RadiusType.LEFT_HALF_OVAL
- : RadiusType.RIGHT_HALF_OVAL;
- updateRadiusWith(mSizeType, mRadiusType, mTargets.size());
-
- fadeOut();
-
- mOnDragEndListener.ifPresent(
- onDragEndListener -> onDragEndListener.onDragEnd(mPosition));
- }
- });
-
-
- initListView();
- updateStrokeWith(getResources().getConfiguration().uiMode, mAlignment);
- }
-
- @Override
- public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,
- @NonNull MotionEvent event) {
- final int currentRawX = (int) event.getRawX();
- final int currentRawY = (int) event.getRawY();
-
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- fadeIn();
-
- mDownX = currentRawX;
- mDownY = currentRawY;
- mRelativeToPointerDownX = mCurrentLayoutParams.x - mDownX;
- mRelativeToPointerDownY = mCurrentLayoutParams.y - mDownY;
- mListView.animate().translationX(0);
- break;
- case MotionEvent.ACTION_MOVE:
- if (mIsDragging
- || hasExceededTouchSlop(mDownX, mDownY, currentRawX, currentRawY)) {
- if (!mIsDragging) {
- mIsDragging = true;
- setRadius(mRadius, RadiusType.OVAL);
- setInset(0, 0);
- }
-
- mTemporaryShapeType =
- isMovingTowardsScreenEdge(mAlignment, currentRawX, mDownX)
- ? ShapeType.HALF_OVAL
- : ShapeType.OVAL;
- final int newWindowX = currentRawX + mRelativeToPointerDownX;
- final int newWindowY = currentRawY + mRelativeToPointerDownY;
- mCurrentLayoutParams.x =
- constrain(newWindowX, getMinWindowX(), getMaxWindowX());
- mCurrentLayoutParams.y = constrain(newWindowY, MIN_WINDOW_Y, getMaxWindowY());
- mWindowManager.updateViewLayout(this, mCurrentLayoutParams);
- }
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- if (mIsDragging) {
- mIsDragging = false;
-
- final int minX = getMinWindowX();
- final int maxX = getMaxWindowX();
- final int endX = mCurrentLayoutParams.x > ((minX + maxX) / 2)
- ? maxX : minX;
- final int endY = mCurrentLayoutParams.y;
- snapToLocation(endX, endY);
-
- setShapeType(mTemporaryShapeType);
-
- // Avoid triggering the listener of the item.
- return true;
- }
-
- // Must switch the oval shape type before tapping the corresponding item in the
- // list view, otherwise it can't work on it.
- if (!isOvalShape()) {
- setShapeType(ShapeType.OVAL);
-
- return true;
- }
-
- fadeOut();
- break;
- default: // Do nothing
- }
-
- // not consume all the events here because keeping the scroll behavior of list view.
- return false;
- }
-
- @Override
- public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
- // Do Nothing
- }
-
- @Override
- public void onRequestDisallowInterceptTouchEvent(boolean b) {
- // Do Nothing
- }
-
- void show() {
- if (isShowing()) {
- return;
- }
-
- mIsShowing = true;
- mWindowManager.addView(this, mCurrentLayoutParams);
-
- setOnApplyWindowInsetsListener((view, insets) -> onWindowInsetsApplied(insets));
- setSystemGestureExclusion();
- }
-
- void hide() {
- if (!isShowing()) {
- return;
- }
-
- mIsShowing = false;
- mDragAnimator.cancel();
- mWindowManager.removeView(this);
-
- setOnApplyWindowInsetsListener(null);
- setSystemGestureExclusion();
- }
-
- boolean isShowing() {
- return mIsShowing;
- }
-
- boolean isOvalShape() {
- return mShapeType == ShapeType.OVAL;
- }
-
- void onTargetsChanged(List<AccessibilityTarget> newTargets) {
- fadeIn();
-
- mTargets.clear();
- mTargets.addAll(newTargets);
- onEnabledFeaturesChanged();
-
- updateRadiusWith(mSizeType, mRadiusType, mTargets.size());
- updateScrollModeWith(hasExceededMaxLayoutHeight());
- setSystemGestureExclusion();
-
- fadeOut();
- }
-
- void setSizeType(@SizeType int newSizeType) {
- fadeIn();
-
- mSizeType = newSizeType;
-
- updateItemViewWith(newSizeType);
- updateRadiusWith(newSizeType, mRadiusType, mTargets.size());
-
- // When the icon sized changed, the menu size and location will be impacted.
- updateLocationWith(mPosition);
- updateScrollModeWith(hasExceededMaxLayoutHeight());
- updateOffsetWith(mShapeType, mAlignment);
- setSystemGestureExclusion();
-
- fadeOut();
- }
-
- void setShapeType(@ShapeType int newShapeType) {
- fadeIn();
-
- mShapeType = newShapeType;
-
- updateOffsetWith(newShapeType, mAlignment);
-
- setOnTouchListener(
- newShapeType == ShapeType.OVAL
- ? null
- : (view, event) -> onTouched(event));
-
- fadeOut();
- }
-
- public void setOnDragEndListener(OnDragEndListener onDragEndListener) {
- mOnDragEndListener = Optional.ofNullable(onDragEndListener);
- }
-
- void startTranslateXAnimation() {
- fadeIn();
-
- final float toXValue = (mAlignment == Alignment.RIGHT)
- ? ANIMATION_TO_X_VALUE
- : -ANIMATION_TO_X_VALUE;
- final TranslateAnimation animation =
- new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0,
- Animation.RELATIVE_TO_SELF, toXValue,
- Animation.RELATIVE_TO_SELF, 0,
- Animation.RELATIVE_TO_SELF, 0);
- animation.setDuration(ANIMATION_DURATION_MS);
- animation.setRepeatMode(Animation.REVERSE);
- animation.setInterpolator(new OvershootInterpolator());
- animation.setRepeatCount(Animation.INFINITE);
- animation.setStartOffset(ANIMATION_START_OFFSET);
- mListView.startAnimation(animation);
- }
-
- void stopTranslateXAnimation() {
- mListView.clearAnimation();
-
- fadeOut();
- }
-
- Rect getWindowLocationOnScreen() {
- final int left = mCurrentLayoutParams.x;
- final int top = mCurrentLayoutParams.y;
- return new Rect(left, top, left + getWindowWidth(), top + getWindowHeight());
- }
-
- void updateOpacityWith(boolean isFadeEffectEnabled, float newOpacityValue) {
- mIsFadeEffectEnabled = isFadeEffectEnabled;
- mFadeOutValue = newOpacityValue;
-
- mFadeOutAnimator.cancel();
- mFadeOutAnimator.setFloatValues(1.0f, mFadeOutValue);
- setAlpha(mIsFadeEffectEnabled ? mFadeOutValue : /* completely opaque */ 1.0f);
- }
-
- void onEnabledFeaturesChanged() {
- mAdapter.notifyDataSetChanged();
- }
-
- @VisibleForTesting
- void fadeIn() {
- if (!mIsFadeEffectEnabled) {
- return;
- }
-
- mFadeOutAnimator.cancel();
- mUiHandler.removeCallbacksAndMessages(null);
- mUiHandler.post(() -> setAlpha(/* completely opaque */ 1.0f));
- }
-
- @VisibleForTesting
- void fadeOut() {
- if (!mIsFadeEffectEnabled) {
- return;
- }
-
- mUiHandler.postDelayed(() -> mFadeOutAnimator.start(), FADE_EFFECT_DURATION_MS);
- }
-
- private boolean onTouched(MotionEvent event) {
- final int action = event.getAction();
- final int currentX = (int) event.getX();
- final int currentY = (int) event.getY();
-
- final int marginStartEnd = getMarginStartEndWith(mLastConfiguration);
- final Rect touchDelegateBounds =
- new Rect(marginStartEnd, mMargin, marginStartEnd + getLayoutWidth(),
- mMargin + getLayoutHeight());
- if (action == MotionEvent.ACTION_DOWN
- && touchDelegateBounds.contains(currentX, currentY)) {
- mIsDownInEnlargedTouchArea = true;
- }
-
- if (!mIsDownInEnlargedTouchArea) {
- return false;
- }
-
- if (action == MotionEvent.ACTION_UP
- || action == MotionEvent.ACTION_CANCEL) {
- mIsDownInEnlargedTouchArea = false;
- }
-
- // In order to correspond to the correct item of list view.
- event.setLocation(currentX - mMargin, currentY - mMargin);
- return mListView.dispatchTouchEvent(event);
- }
-
- private WindowInsets onWindowInsetsApplied(WindowInsets insets) {
- final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
- final Rect displayWindowInsetsRect = getDisplayInsets(windowMetrics).toRect();
- if (!displayWindowInsetsRect.equals(mDisplayInsetsRect)) {
- updateDisplaySizeWith(windowMetrics);
- updateLocationWith(mPosition);
- }
-
- final Rect imeInsetsRect = windowMetrics.getWindowInsets().getInsets(ime()).toRect();
- if (!imeInsetsRect.equals(mImeInsetsRect)) {
- if (isImeVisible(imeInsetsRect)) {
- mImeInsetsRect.set(imeInsetsRect);
- } else {
- mImeInsetsRect.setEmpty();
- }
-
- updateLocationWith(mPosition);
- }
-
- return insets;
- }
-
- private boolean isMovingTowardsScreenEdge(@Alignment int side, int currentRawX, int downX) {
- return (side == Alignment.RIGHT && currentRawX > downX)
- || (side == Alignment.LEFT && downX > currentRawX);
- }
-
- private boolean isImeVisible(Rect imeInsetsRect) {
- return imeInsetsRect.left != 0 || imeInsetsRect.top != 0 || imeInsetsRect.right != 0
- || imeInsetsRect.bottom != 0;
- }
-
- private boolean hasExceededTouchSlop(int startX, int startY, int endX, int endY) {
- return (sq(endX - startX) + sq(endY - startY)) > mSquareScaledTouchSlop;
- }
-
- private void setRadius(float radius, @RadiusType int type) {
- getMenuGradientDrawable().setCornerRadii(createRadii(radius, type));
- }
-
- private float[] createRadii(float radius, @RadiusType int type) {
- if (type == RadiusType.LEFT_HALF_OVAL) {
- return new float[]{radius, radius, 0.0f, 0.0f, 0.0f, 0.0f, radius, radius};
- }
-
- if (type == RadiusType.RIGHT_HALF_OVAL) {
- return new float[]{0.0f, 0.0f, radius, radius, radius, radius, 0.0f, 0.0f};
- }
-
- return new float[]{radius, radius, radius, radius, radius, radius, radius, radius};
- }
-
- private Handler createUiHandler() {
- return new Handler(requireNonNull(Looper.myLooper(), "looper must not be null"));
- }
-
- private void updateDimensions() {
- final Resources res = getResources();
-
- updateDisplaySizeWith(mWindowManager.getCurrentWindowMetrics());
-
- mMargin =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_margin);
- mInset =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_inset);
-
- mSquareScaledTouchSlop =
- sq(ViewConfiguration.get(getContext()).getScaledTouchSlop());
-
- updateItemViewDimensionsWith(mSizeType);
- }
-
- private void updateDisplaySizeWith(WindowMetrics metrics) {
- final Rect displayBounds = metrics.getBounds();
- final Insets displayInsets = getDisplayInsets(metrics);
- mDisplayInsetsRect.set(displayInsets.toRect());
- displayBounds.inset(displayInsets);
- mDisplayWidth = displayBounds.width();
- mDisplayHeight = displayBounds.height();
- }
-
- private void updateItemViewDimensionsWith(@SizeType int sizeType) {
- final Resources res = getResources();
- final int paddingResId =
- sizeType == SizeType.SMALL
- ? R.dimen.accessibility_floating_menu_small_padding
- : R.dimen.accessibility_floating_menu_large_padding;
- mPadding = res.getDimensionPixelSize(paddingResId);
-
- final int iconResId =
- sizeType == SizeType.SMALL
- ? R.dimen.accessibility_floating_menu_small_width_height
- : R.dimen.accessibility_floating_menu_large_width_height;
- mIconWidth = res.getDimensionPixelSize(iconResId);
- mIconHeight = mIconWidth;
- }
-
- private void updateItemViewWith(@SizeType int sizeType) {
- updateItemViewDimensionsWith(sizeType);
-
- mAdapter.setItemPadding(mPadding);
- mAdapter.setIconWidthHeight(mIconWidth);
- mAdapter.notifyDataSetChanged();
- }
-
- private void initListView() {
- final Drawable background =
- getContext().getDrawable(R.drawable.accessibility_floating_menu_background);
- final LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
- final LayoutParams layoutParams =
- new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- mListView.setLayoutParams(layoutParams);
- final InstantInsetLayerDrawable layerDrawable =
- new InstantInsetLayerDrawable(new Drawable[]{background});
- mListView.setBackground(layerDrawable);
- mListView.setAdapter(mAdapter);
- mListView.setLayoutManager(layoutManager);
- mListView.addOnItemTouchListener(this);
- mListView.animate().setInterpolator(new OvershootInterpolator());
- mListView.setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(mListView) {
- @NonNull
- @Override
- public AccessibilityDelegateCompat getItemDelegate() {
- return new ItemDelegateCompat(this,
- AccessibilityFloatingMenuView.this);
- }
- });
-
- updateListViewWith(mLastConfiguration);
-
- addView(mListView);
- }
-
- private void updateListViewWith(Configuration configuration) {
- updateMarginWith(configuration);
-
- final int elevation =
- getResources().getDimensionPixelSize(R.dimen.accessibility_floating_menu_elevation);
- mListView.setElevation(elevation);
- }
-
- private WindowManager.LayoutParams createDefaultLayoutParams() {
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
- WindowManager.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
- PixelFormat.TRANSLUCENT);
- params.receiveInsetsIgnoringZOrder = true;
- params.privateFlags |= PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
- params.windowAnimations = android.R.style.Animation_Translucent;
- params.gravity = Gravity.START | Gravity.TOP;
- params.x = (mAlignment == Alignment.RIGHT) ? getMaxWindowX() : getMinWindowX();
-// params.y = (int) (mPosition.getPercentageY() * getMaxWindowY());
- final int currentLayoutY = (int) (mPosition.getPercentageY() * getMaxWindowY());
- params.y = Math.max(MIN_WINDOW_Y, currentLayoutY - getInterval());
- updateAccessibilityTitle(params);
- return params;
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- mLastConfiguration.setTo(newConfig);
-
- final int diff = newConfig.diff(mLastConfiguration);
- if ((diff & ActivityInfo.CONFIG_LOCALE) != 0) {
- updateAccessibilityTitle(mCurrentLayoutParams);
- }
-
- updateDimensions();
- updateListViewWith(newConfig);
- updateItemViewWith(mSizeType);
- updateColor();
- updateStrokeWith(newConfig.uiMode, mAlignment);
- updateLocationWith(mPosition);
- updateRadiusWith(mSizeType, mRadiusType, mTargets.size());
- updateScrollModeWith(hasExceededMaxLayoutHeight());
- setSystemGestureExclusion();
- }
-
- @VisibleForTesting
- void snapToLocation(int endX, int endY) {
- mDragAnimator.cancel();
- mDragAnimator.removeAllUpdateListeners();
- mDragAnimator.addUpdateListener(anim -> onDragAnimationUpdate(anim, endX, endY));
- mDragAnimator.start();
- }
-
- private void onDragAnimationUpdate(ValueAnimator animator, int endX, int endY) {
- float value = (float) animator.getAnimatedValue();
- final int newX = (int) (((1 - value) * mCurrentLayoutParams.x) + (value * endX));
- final int newY = (int) (((1 - value) * mCurrentLayoutParams.y) + (value * endY));
-
- mCurrentLayoutParams.x = newX;
- mCurrentLayoutParams.y = newY;
- mWindowManager.updateViewLayout(this, mCurrentLayoutParams);
- }
-
- private int getMinWindowX() {
- return -getMarginStartEndWith(mLastConfiguration);
- }
-
- private int getMaxWindowX() {
- return mDisplayWidth - getMarginStartEndWith(mLastConfiguration) - getLayoutWidth();
- }
-
- private int getMaxWindowY() {
- return mDisplayHeight - getWindowHeight();
- }
-
- private InstantInsetLayerDrawable getMenuLayerDrawable() {
- return (InstantInsetLayerDrawable) mListView.getBackground();
- }
-
- private GradientDrawable getMenuGradientDrawable() {
- return (GradientDrawable) getMenuLayerDrawable().getDrawable(INDEX_MENU_ITEM);
- }
-
- private Insets getDisplayInsets(WindowMetrics metrics) {
- return metrics.getWindowInsets().getInsetsIgnoringVisibility(
- systemBars() | displayCutout());
- }
-
- /**
- * Updates the floating menu to be fixed at the side of the display.
- */
- private void updateLocationWith(Position position) {
- final @Alignment int alignment = transformToAlignment(position.getPercentageX());
- mCurrentLayoutParams.x = (alignment == Alignment.RIGHT) ? getMaxWindowX() : getMinWindowX();
- final int currentLayoutY = (int) (position.getPercentageY() * getMaxWindowY());
- mCurrentLayoutParams.y = Math.max(MIN_WINDOW_Y, currentLayoutY - getInterval());
- mWindowManager.updateViewLayout(this, mCurrentLayoutParams);
- }
-
- /**
- * Gets the moving interval to not overlap between the keyboard and menu view.
- *
- * @return the moving interval if they overlap each other, otherwise 0.
- */
- private int getInterval() {
- final int currentLayoutY = (int) (mPosition.getPercentageY() * getMaxWindowY());
- final int imeY = mDisplayHeight - mImeInsetsRect.bottom;
- final int layoutBottomY = currentLayoutY + getWindowHeight();
-
- return layoutBottomY > imeY ? (layoutBottomY - imeY) : 0;
- }
-
- private void updateMarginWith(Configuration configuration) {
- // Avoid overlapping with system bars under landscape mode, update the margins of the menu
- // to align the edge of system bars.
- final int marginStartEnd = getMarginStartEndWith(configuration);
- final LayoutParams layoutParams = (FrameLayout.LayoutParams) mListView.getLayoutParams();
- layoutParams.setMargins(marginStartEnd, mMargin, marginStartEnd, mMargin);
- mListView.setLayoutParams(layoutParams);
- }
-
- private void updateOffsetWith(@ShapeType int shapeType, @Alignment int side) {
- final float halfWidth = getLayoutWidth() / 2.0f;
- final float offset = (shapeType == ShapeType.OVAL) ? 0 : halfWidth;
- mListView.animate().translationX(side == Alignment.RIGHT ? offset : -offset);
- }
-
- private void updateScrollModeWith(boolean hasExceededMaxLayoutHeight) {
- mListView.setOverScrollMode(hasExceededMaxLayoutHeight
- ? OVER_SCROLL_ALWAYS
- : OVER_SCROLL_NEVER);
- }
-
- private void updateColor() {
- final int menuColorResId = R.color.accessibility_floating_menu_background;
- getMenuGradientDrawable().setColor(getResources().getColor(menuColorResId));
- }
-
- private void updateStrokeWith(int uiMode, @Alignment int side) {
- updateInsetWith(uiMode, side);
-
- final boolean isNightMode =
- (uiMode & Configuration.UI_MODE_NIGHT_MASK)
- == Configuration.UI_MODE_NIGHT_YES;
- final Resources res = getResources();
- final int width =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_width);
- final int strokeWidth = isNightMode ? width : 0;
- final int strokeColor = res.getColor(R.color.accessibility_floating_menu_stroke_dark);
- getMenuGradientDrawable().setStroke(strokeWidth, strokeColor);
- }
-
- private void updateRadiusWith(@SizeType int sizeType, @RadiusType int radiusType,
- int itemCount) {
- mRadius =
- getResources().getDimensionPixelSize(getRadiusResId(sizeType, itemCount));
- setRadius(mRadius, radiusType);
- }
-
- private void updateInsetWith(int uiMode, @Alignment int side) {
- final boolean isNightMode =
- (uiMode & Configuration.UI_MODE_NIGHT_MASK)
- == Configuration.UI_MODE_NIGHT_YES;
-
- final int layerInset = isNightMode ? mInset : 0;
- final int insetLeft = (side == Alignment.LEFT) ? layerInset : 0;
- final int insetRight = (side == Alignment.RIGHT) ? layerInset : 0;
- setInset(insetLeft, insetRight);
- }
-
- private void updateAccessibilityTitle(WindowManager.LayoutParams params) {
- params.accessibilityTitle = getResources().getString(
- com.android.internal.R.string.accessibility_select_shortcut_menu_title);
- }
-
- private void setInset(int left, int right) {
- final LayerDrawable layerDrawable = getMenuLayerDrawable();
- if (layerDrawable.getLayerInsetLeft(INDEX_MENU_ITEM) == left
- && layerDrawable.getLayerInsetRight(INDEX_MENU_ITEM) == right) {
- return;
- }
-
- layerDrawable.setLayerInset(INDEX_MENU_ITEM, left, 0, right, 0);
- }
-
- @VisibleForTesting
- boolean hasExceededMaxLayoutHeight() {
- return calculateActualLayoutHeight() > getMaxLayoutHeight();
- }
-
- @Alignment
- private int transformToAlignment(@FloatRange(from = 0.0, to = 1.0) float percentageX) {
- return (percentageX < 0.5f) ? Alignment.LEFT : Alignment.RIGHT;
- }
-
- private float transformCurrentPercentageXToEdge() {
- final float percentageX = calculateCurrentPercentageX();
- return (percentageX < 0.5) ? 0.0f : 1.0f;
- }
-
- private float calculateCurrentPercentageX() {
- return mCurrentLayoutParams.x / (float) getMaxWindowX();
- }
-
- private float calculateCurrentPercentageY() {
- return mCurrentLayoutParams.y / (float) getMaxWindowY();
- }
-
- private int calculateActualLayoutHeight() {
- return (mPadding + mIconHeight) * mTargets.size() + mPadding;
- }
-
- private int getMarginStartEndWith(Configuration configuration) {
- return configuration != null
- && configuration.orientation == ORIENTATION_PORTRAIT
- ? mMargin : 0;
- }
-
- private @DimenRes int getRadiusResId(@SizeType int sizeType, int itemCount) {
- return sizeType == SizeType.SMALL
- ? getSmallSizeResIdWith(itemCount)
- : getLargeSizeResIdWith(itemCount);
- }
-
- private int getSmallSizeResIdWith(int itemCount) {
- return itemCount > 1
- ? R.dimen.accessibility_floating_menu_small_multiple_radius
- : R.dimen.accessibility_floating_menu_small_single_radius;
- }
-
- private int getLargeSizeResIdWith(int itemCount) {
- return itemCount > 1
- ? R.dimen.accessibility_floating_menu_large_multiple_radius
- : R.dimen.accessibility_floating_menu_large_single_radius;
- }
-
- @VisibleForTesting
- Rect getAvailableBounds() {
- return new Rect(0, 0, mDisplayWidth - getWindowWidth(),
- mDisplayHeight - getWindowHeight());
- }
-
- private int getMaxLayoutHeight() {
- return mDisplayHeight - mMargin * 2;
- }
-
- private int getLayoutWidth() {
- return mPadding * 2 + mIconWidth;
- }
-
- private int getLayoutHeight() {
- return Math.min(getMaxLayoutHeight(), calculateActualLayoutHeight());
- }
-
- private int getWindowWidth() {
- return getMarginStartEndWith(mLastConfiguration) * 2 + getLayoutWidth();
- }
-
- private int getWindowHeight() {
- return Math.min(mDisplayHeight, mMargin * 2 + getLayoutHeight());
- }
-
- private void setSystemGestureExclusion() {
- final Rect excludeZone =
- new Rect(0, 0, getWindowWidth(), getWindowHeight());
- post(() -> setSystemGestureExclusionRects(
- mIsShowing
- ? Collections.singletonList(excludeZone)
- : Collections.emptyList()));
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java
deleted file mode 100644
index 61fc7ed..0000000
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import static android.util.TypedValue.COMPLEX_UNIT_PX;
-import static android.view.View.MeasureSpec.AT_MOST;
-import static android.view.View.MeasureSpec.UNSPECIFIED;
-
-import android.annotation.UiContext;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.CornerPathEffect;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.ShapeDrawable;
-import android.os.Bundle;
-import android.text.method.MovementMethod;
-import android.util.DisplayMetrics;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.settingslib.Utils;
-import com.android.systemui.R;
-import com.android.systemui.recents.TriangleShape;
-
-/**
- * Base tooltip view that shows the information about the operation of the
- * Accessibility floating menu. In addition, the anchor view is only for {@link
- * AccessibilityFloatingMenuView}, it should be more suited for displaying one-off menus to avoid
- * the performance hit for the extra window.
- */
-class BaseTooltipView extends FrameLayout {
- private int mFontSize;
- private int mTextViewMargin;
- private int mTextViewPadding;
- private int mTextViewCornerRadius;
- private int mArrowMargin;
- private int mArrowWidth;
- private int mArrowHeight;
- private int mArrowCornerRadius;
- private int mScreenWidth;
- private boolean mIsShowing;
- private TextView mTextView;
- private final WindowManager.LayoutParams mCurrentLayoutParams;
- private final WindowManager mWindowManager;
- private final AccessibilityFloatingMenuView mAnchorView;
-
- BaseTooltipView(@UiContext Context context, AccessibilityFloatingMenuView anchorView) {
- super(context);
- mWindowManager = context.getSystemService(WindowManager.class);
- mAnchorView = anchorView;
- mCurrentLayoutParams = createDefaultLayoutParams();
-
- initViews();
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
-
- mAnchorView.onConfigurationChanged(newConfig);
- updateTooltipView();
-
- mWindowManager.updateViewLayout(this, mCurrentLayoutParams);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
- hide();
- }
-
- return super.onTouchEvent(event);
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
-
- info.addAction(AccessibilityAction.ACTION_DISMISS);
- }
-
- @Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (action == AccessibilityAction.ACTION_DISMISS.getId()) {
- hide();
- return true;
- }
-
- return super.performAccessibilityAction(action, arguments);
- }
-
- void show() {
- if (isShowing()) {
- return;
- }
-
- mIsShowing = true;
- updateTooltipView();
-
- mWindowManager.addView(this, mCurrentLayoutParams);
- }
-
- void hide() {
- if (!isShowing()) {
- return;
- }
-
- mIsShowing = false;
- mWindowManager.removeView(this);
- }
-
- void setDescription(CharSequence text) {
- mTextView.setText(text);
- }
-
- void setMovementMethod(MovementMethod movement) {
- mTextView.setMovementMethod(movement);
- }
-
- private boolean isShowing() {
- return mIsShowing;
- }
-
- private void initViews() {
- final View contentView =
- LayoutInflater.from(getContext()).inflate(
- R.layout.accessibility_floating_menu_tooltip, this, false);
-
- mTextView = contentView.findViewById(R.id.text);
-
- addView(contentView);
- }
-
- private static WindowManager.LayoutParams createDefaultLayoutParams() {
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
- WindowManager.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
- PixelFormat.TRANSLUCENT);
- params.windowAnimations = android.R.style.Animation_Translucent;
- params.gravity = Gravity.START | Gravity.TOP;
-
- return params;
- }
-
- private void updateDimensions() {
- final Resources res = getResources();
- final DisplayMetrics dm = res.getDisplayMetrics();
- mScreenWidth = dm.widthPixels;
- mArrowWidth =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_arrow_width);
- mArrowHeight =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_arrow_height);
- mArrowMargin =
- res.getDimensionPixelSize(
- R.dimen.accessibility_floating_tooltip_arrow_margin);
- mArrowCornerRadius =
- res.getDimensionPixelSize(
- R.dimen.accessibility_floating_tooltip_arrow_corner_radius);
- mFontSize =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_font_size);
- mTextViewMargin =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_margin);
- mTextViewPadding =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_padding);
- mTextViewCornerRadius =
- res.getDimensionPixelSize(
- R.dimen.accessibility_floating_tooltip_text_corner_radius);
- }
-
- private void updateTooltipView() {
- updateDimensions();
- updateTextView();
-
- final Rect anchorViewLocation = mAnchorView.getWindowLocationOnScreen();
- updateArrowWith(anchorViewLocation);
- updateWidthWith(anchorViewLocation);
- updateLocationWith(anchorViewLocation);
- }
-
- private void updateTextView() {
- mTextView.setTextSize(COMPLEX_UNIT_PX, mFontSize);
- mTextView.setPadding(mTextViewPadding, mTextViewPadding, mTextViewPadding,
- mTextViewPadding);
-
- final GradientDrawable gradientDrawable = (GradientDrawable) mTextView.getBackground();
- gradientDrawable.setCornerRadius(mTextViewCornerRadius);
- gradientDrawable.setColor(Utils.getColorAttrDefaultColor(getContext(),
- com.android.internal.R.attr.colorAccentPrimary));
- }
-
- private void updateArrowWith(Rect anchorViewLocation) {
- final boolean isAnchorViewOnLeft = isAnchorViewOnLeft(anchorViewLocation);
- final View arrowView = findViewById(isAnchorViewOnLeft
- ? R.id.arrow_left
- : R.id.arrow_right);
- arrowView.setVisibility(VISIBLE);
- drawArrow(arrowView, isAnchorViewOnLeft);
-
- final LinearLayout.LayoutParams layoutParams =
- (LinearLayout.LayoutParams) arrowView.getLayoutParams();
- layoutParams.width = mArrowWidth;
- layoutParams.height = mArrowHeight;
-
- final int leftMargin = isAnchorViewOnLeft ? 0 : mArrowMargin;
- final int rightMargin = isAnchorViewOnLeft ? mArrowMargin : 0;
- layoutParams.setMargins(leftMargin, 0, rightMargin, 0);
- arrowView.setLayoutParams(layoutParams);
- }
-
- private void updateWidthWith(Rect anchorViewLocation) {
- final ViewGroup.LayoutParams layoutParams = mTextView.getLayoutParams();
- layoutParams.width = getTextWidthWith(anchorViewLocation);
- mTextView.setLayoutParams(layoutParams);
- }
-
- private void updateLocationWith(Rect anchorViewLocation) {
- mCurrentLayoutParams.x = isAnchorViewOnLeft(anchorViewLocation)
- ? anchorViewLocation.width()
- : mScreenWidth - getWindowWidthWith(anchorViewLocation)
- - anchorViewLocation.width();
- mCurrentLayoutParams.y =
- anchorViewLocation.centerY() - (getTextHeightWith(anchorViewLocation) / 2);
- }
-
- private void drawArrow(View view, boolean isPointingLeft) {
- final ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
- final TriangleShape triangleShape =
- TriangleShape.createHorizontal(layoutParams.width, layoutParams.height,
- isPointingLeft);
- final ShapeDrawable arrowDrawable = new ShapeDrawable(triangleShape);
- final Paint arrowPaint = arrowDrawable.getPaint();
- arrowPaint.setColor(Utils.getColorAttrDefaultColor(getContext(),
- com.android.internal.R.attr.colorAccentPrimary));
- final CornerPathEffect effect = new CornerPathEffect(mArrowCornerRadius);
- arrowPaint.setPathEffect(effect);
- view.setBackground(arrowDrawable);
- }
-
- private boolean isAnchorViewOnLeft(Rect anchorViewLocation) {
- return anchorViewLocation.left < (mScreenWidth / 2);
- }
-
- private int getTextWidthWith(Rect anchorViewLocation) {
- final int widthSpec =
- MeasureSpec.makeMeasureSpec(getAvailableTextWidthWith(anchorViewLocation), AT_MOST);
- final int heightSpec =
- MeasureSpec.makeMeasureSpec(0, UNSPECIFIED);
- mTextView.measure(widthSpec, heightSpec);
- return mTextView.getMeasuredWidth();
- }
-
- private int getTextHeightWith(Rect anchorViewLocation) {
- final int widthSpec =
- MeasureSpec.makeMeasureSpec(getAvailableTextWidthWith(anchorViewLocation), AT_MOST);
- final int heightSpec =
- MeasureSpec.makeMeasureSpec(0, UNSPECIFIED);
- mTextView.measure(widthSpec, heightSpec);
- return mTextView.getMeasuredHeight();
- }
-
- private int getAvailableTextWidthWith(Rect anchorViewLocation) {
- return mScreenWidth - anchorViewLocation.width() - mArrowWidth - mArrowMargin
- - mTextViewMargin;
- }
-
- private int getWindowWidthWith(Rect anchorViewLocation) {
- return getTextWidthWith(anchorViewLocation) + mArrowWidth + mArrowMargin;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DockTooltipView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DockTooltipView.java
deleted file mode 100644
index 49056a6..0000000
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DockTooltipView.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import android.content.Context;
-
-import com.android.systemui.R;
-
-/**
- * Dock tooltip view that shows the info about moving the Accessibility button to the edge to hide.
- */
-class DockTooltipView extends BaseTooltipView {
- private final AccessibilityFloatingMenuView mAnchorView;
-
- DockTooltipView(Context context, AccessibilityFloatingMenuView anchorView) {
- super(context, anchorView);
- mAnchorView = anchorView;
-
- setDescription(
- getContext().getText(R.string.accessibility_floating_button_docking_tooltip));
- }
-
- @Override
- void hide() {
- super.hide();
-
- mAnchorView.stopTranslateXAnimation();
- }
-
- @Override
- void show() {
- super.show();
-
- mAnchorView.startTranslateXAnimation();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompat.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompat.java
deleted file mode 100644
index 93b0676..0000000
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompat.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
-import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
-
-import com.android.systemui.R;
-import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.ShapeType;
-
-import java.lang.ref.WeakReference;
-
-/**
- * An accessibility item delegate for the individual items of the list view
- * {@link AccessibilityFloatingMenuView}.
- */
-final class ItemDelegateCompat extends RecyclerViewAccessibilityDelegate.ItemDelegate {
- private final WeakReference<AccessibilityFloatingMenuView> mMenuViewRef;
-
- ItemDelegateCompat(@NonNull RecyclerViewAccessibilityDelegate recyclerViewDelegate,
- AccessibilityFloatingMenuView menuView) {
- super(recyclerViewDelegate);
- this.mMenuViewRef = new WeakReference<>(menuView);
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
-
- if (mMenuViewRef.get() == null) {
- return;
- }
- final AccessibilityFloatingMenuView menuView = mMenuViewRef.get();
-
- final Resources res = menuView.getResources();
- final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveTopLeft =
- new AccessibilityNodeInfoCompat.AccessibilityActionCompat(R.id.action_move_top_left,
- res.getString(
- R.string.accessibility_floating_button_action_move_top_left));
- info.addAction(moveTopLeft);
-
- final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveTopRight =
- new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
- R.id.action_move_top_right,
- res.getString(
- R.string.accessibility_floating_button_action_move_top_right));
- info.addAction(moveTopRight);
-
- final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveBottomLeft =
- new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
- R.id.action_move_bottom_left,
- res.getString(
- R.string.accessibility_floating_button_action_move_bottom_left));
- info.addAction(moveBottomLeft);
-
- final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveBottomRight =
- new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
- R.id.action_move_bottom_right,
- res.getString(
- R.string.accessibility_floating_button_action_move_bottom_right));
- info.addAction(moveBottomRight);
-
- final int moveEdgeId = menuView.isOvalShape()
- ? R.id.action_move_to_edge_and_hide
- : R.id.action_move_out_edge_and_show;
- final int moveEdgeTextResId = menuView.isOvalShape()
- ? R.string.accessibility_floating_button_action_move_to_edge_and_hide_to_half
- : R.string.accessibility_floating_button_action_move_out_edge_and_show;
- final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveToOrOutEdge =
- new AccessibilityNodeInfoCompat.AccessibilityActionCompat(moveEdgeId,
- res.getString(moveEdgeTextResId));
- info.addAction(moveToOrOutEdge);
- }
-
- @Override
- public boolean performAccessibilityAction(View host, int action, Bundle args) {
- if (mMenuViewRef.get() == null) {
- return super.performAccessibilityAction(host, action, args);
- }
- final AccessibilityFloatingMenuView menuView = mMenuViewRef.get();
-
- menuView.fadeIn();
-
- final Rect bounds = menuView.getAvailableBounds();
- if (action == R.id.action_move_top_left) {
- menuView.setShapeType(ShapeType.OVAL);
- menuView.snapToLocation(bounds.left, bounds.top);
- return true;
- }
-
- if (action == R.id.action_move_top_right) {
- menuView.setShapeType(ShapeType.OVAL);
- menuView.snapToLocation(bounds.right, bounds.top);
- return true;
- }
-
- if (action == R.id.action_move_bottom_left) {
- menuView.setShapeType(ShapeType.OVAL);
- menuView.snapToLocation(bounds.left, bounds.bottom);
- return true;
- }
-
- if (action == R.id.action_move_bottom_right) {
- menuView.setShapeType(ShapeType.OVAL);
- menuView.snapToLocation(bounds.right, bounds.bottom);
- return true;
- }
-
- if (action == R.id.action_move_to_edge_and_hide) {
- menuView.setShapeType(ShapeType.HALF_OVAL);
- return true;
- }
-
- if (action == R.id.action_move_out_edge_and_show) {
- menuView.setShapeType(ShapeType.OVAL);
- return true;
- }
-
- return super.performAccessibilityAction(host, action, args);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MigrationTooltipView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MigrationTooltipView.java
deleted file mode 100644
index e4f3e31..0000000
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MigrationTooltipView.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_BUTTON_COMPONENT_NAME;
-
-import android.content.Context;
-import android.content.Intent;
-import android.provider.Settings;
-import android.text.method.LinkMovementMethod;
-
-import com.android.systemui.R;
-
-/**
- * Migration tooltip view that shows the information about the Accessibility button was replaced
- * with the floating menu.
- */
-class MigrationTooltipView extends BaseTooltipView {
- MigrationTooltipView(Context context, AccessibilityFloatingMenuView anchorView) {
- super(context, anchorView);
-
- final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.putExtra(Intent.EXTRA_COMPONENT_NAME,
- ACCESSIBILITY_BUTTON_COMPONENT_NAME.flattenToShortString());
-
- final AnnotationLinkSpan.LinkInfo linkInfo = new AnnotationLinkSpan.LinkInfo(
- AnnotationLinkSpan.LinkInfo.DEFAULT_ANNOTATION,
- v -> {
- getContext().startActivity(intent);
- hide();
- });
-
- final int textResId = R.string.accessibility_floating_button_migration_tooltip;
- setDescription(AnnotationLinkSpan.linkify(getContext().getText(textResId), linkInfo));
- setMovementMethod(LinkMovementMethod.getInstance());
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index 436f9df..1f6f6d9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -46,6 +46,8 @@
private var isDeviceFolded: Boolean = false
private val isSideFps: Boolean
+ private val isReverseDefaultRotation =
+ context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
private val screenSizeFoldProvider: ScreenSizeFoldProvider = ScreenSizeFoldProvider(context)
var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1)
set(value) {
@@ -76,7 +78,7 @@
isSideFps = sideFps
val displayInfo = DisplayInfo()
context.display?.getDisplayInfo(displayInfo)
- if (isSideFps && displayInfo.rotation == Surface.ROTATION_180) {
+ if (isSideFps && getRotationFromDefault(displayInfo.rotation) == Surface.ROTATION_180) {
iconView.rotation = 180f
}
screenSizeFoldProvider.registerCallback(this, context.mainExecutor)
@@ -86,7 +88,7 @@
private fun updateIconSideFps(@BiometricState lastState: Int, @BiometricState newState: Int) {
val displayInfo = DisplayInfo()
context.display?.getDisplayInfo(displayInfo)
- val rotation = displayInfo.rotation
+ val rotation = getRotationFromDefault(displayInfo.rotation)
val iconAnimation = getSideFpsAnimationForTransition(rotation)
val iconViewOverlayAnimation =
getSideFpsOverlayAnimationForTransition(lastState, newState, rotation) ?: return
@@ -104,7 +106,7 @@
iconView.frame = 0
iconViewOverlay.frame = 0
- if (shouldAnimateIconViewForTransition(lastState, newState)) {
+ if (shouldAnimateSfpsIconViewForTransition(lastState, newState)) {
iconView.playAnimation()
}
@@ -169,6 +171,18 @@
STATE_HELP,
STATE_ERROR -> true
STATE_AUTHENTICATING_ANIMATING_IN,
+ STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
+ STATE_AUTHENTICATED -> true
+ else -> false
+ }
+
+ private fun shouldAnimateSfpsIconViewForTransition(
+ @BiometricState oldState: Int,
+ @BiometricState newState: Int
+ ) = when (newState) {
+ STATE_HELP,
+ STATE_ERROR -> true
+ STATE_AUTHENTICATING_ANIMATING_IN,
STATE_AUTHENTICATING ->
oldState == STATE_ERROR || oldState == STATE_HELP || oldState == STATE_IDLE
STATE_AUTHENTICATED -> true
@@ -217,6 +231,9 @@
return if (id != null) return id else null
}
+ private fun getRotationFromDefault(rotation: Int): Int =
+ if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
+
@RawRes
private fun getSideFpsAnimationForTransition(rotation: Int): Int = when (rotation) {
Surface.ROTATION_90 -> if (isDeviceFolded) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/EllipseOverlapDetectorParams.kt b/packages/SystemUI/src/com/android/systemui/biometrics/EllipseOverlapDetectorParams.kt
new file mode 100644
index 0000000..ab9b690
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/EllipseOverlapDetectorParams.kt
@@ -0,0 +1,13 @@
+package com.android.systemui.biometrics
+
+/**
+ * Collection of parameters used by EllipseOverlapDetector
+ *
+ * [minOverlap] minimum percentage (float from 0-1) needed to be considered a valid overlap
+ *
+ * [targetSize] percentage (defined as a float of 0-1) of sensor that is considered the target,
+ * expands outward from center
+ *
+ * [stepSize] size of each step when iterating over sensor pixel grid
+ */
+class EllipseOverlapDetectorParams(val minOverlap: Float, val targetSize: Float, val stepSize: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 7217f99..64d5518 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -557,7 +557,8 @@
final InstanceId sessionIdProvider = mSessionTracker.getSessionId(
getBiometricSessionType());
final int sessionId = (sessionIdProvider != null) ? sessionIdProvider.getId() : -1;
- final int touchConfigId = BOUNDING_BOX_TOUCH_CONFIG_ID;
+ final int touchConfigId = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_selected_udfps_touch_detection);
SysUiStatsLog.write(SysUiStatsLog.BIOMETRIC_TOUCH_REPORTED, biometricTouchReportedTouchType,
touchConfigId, sessionId, data.getX(), data.getY(), data.getMinor(),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 6680787..45ca24d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -442,7 +442,8 @@
): WindowManager.LayoutParams {
val paddingX = animation?.paddingX ?: 0
val paddingY = animation?.paddingY ?: 0
- if (animation != null && animation.listenForTouchesOutsideView()) {
+ if (!featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) && animation != null &&
+ animation.listenForTouchesOutsideView()) {
flags = flags or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt
index 001fed7..20c3e40 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt
@@ -16,6 +16,9 @@
package com.android.systemui.biometrics.dagger
+import android.content.res.Resources
+import com.android.internal.R
+import com.android.systemui.biometrics.EllipseOverlapDetectorParams
import com.android.systemui.biometrics.udfps.BoundingBoxOverlapDetector
import com.android.systemui.biometrics.udfps.EllipseOverlapDetector
import com.android.systemui.biometrics.udfps.OverlapDetector
@@ -33,10 +36,30 @@
@Provides
@SysUISingleton
fun providesOverlapDetector(featureFlags: FeatureFlags): OverlapDetector {
- return if (featureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) {
- EllipseOverlapDetector()
+ if (featureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) {
+ val selectedOption =
+ Resources.getSystem()
+ .getInteger(R.integer.config_selected_udfps_touch_detection)
+ val values =
+ Resources.getSystem()
+ .getStringArray(R.array.config_udfps_touch_detection_options)[
+ selectedOption]
+ .split(",")
+ .map { it.toFloat() }
+
+ return if (values[0] == 1f) {
+ EllipseOverlapDetector(
+ EllipseOverlapDetectorParams(
+ minOverlap = values[3],
+ targetSize = values[2],
+ stepSize = values[4].toInt()
+ )
+ )
+ } else {
+ BoundingBoxOverlapDetector()
+ }
} else {
- BoundingBoxOverlapDetector()
+ return BoundingBoxOverlapDetector()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt
index 682d38a..2e2970f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt
@@ -18,22 +18,84 @@
import android.graphics.Point
import android.graphics.Rect
-import androidx.annotation.VisibleForTesting
+import android.util.Log
+import com.android.systemui.biometrics.EllipseOverlapDetectorParams
import com.android.systemui.dagger.SysUISingleton
import kotlin.math.cos
-import kotlin.math.pow
import kotlin.math.sin
+private enum class SensorPixelPosition {
+ OUTSIDE, // Pixel that falls outside of sensor circle
+ SENSOR, // Pixel within sensor circle
+ TARGET // Pixel within sensor center target
+}
+
+private val isDebug = true
+private val TAG = "EllipseOverlapDetector"
+
/**
* Approximates the touch as an ellipse and determines whether the ellipse has a sufficient overlap
* with the sensor.
*/
@SysUISingleton
-class EllipseOverlapDetector(private val neededPoints: Int = 2) : OverlapDetector {
-
+class EllipseOverlapDetector(private val params: EllipseOverlapDetectorParams) : OverlapDetector {
override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean {
- val points = calculateSensorPoints(nativeSensorBounds)
- return points.count { checkPoint(it, touchData) } >= neededPoints
+ var isTargetTouched = false
+ var sensorPixels = 0
+ var coveredPixels = 0
+ for (y in nativeSensorBounds.top..nativeSensorBounds.bottom step params.stepSize) {
+ for (x in nativeSensorBounds.left..nativeSensorBounds.right step params.stepSize) {
+ // Check where pixel is within the sensor TODO: (b/265836919) This could be improved
+ // by precomputing these points
+ val pixelPosition =
+ isPartOfSensorArea(
+ x,
+ y,
+ nativeSensorBounds.centerX(),
+ nativeSensorBounds.centerY(),
+ nativeSensorBounds.width() / 2
+ )
+ if (pixelPosition != SensorPixelPosition.OUTSIDE) {
+ sensorPixels++
+
+ // Check if this pixel falls within ellipse touch
+ if (checkPoint(Point(x, y), touchData)) {
+ coveredPixels++
+
+ // Check that at least one covered pixel is within sensor target
+ isTargetTouched =
+ isTargetTouched or (pixelPosition == SensorPixelPosition.TARGET)
+ }
+ }
+ }
+ }
+
+ val percentage: Float = coveredPixels.toFloat() / sensorPixels
+ if (isDebug) {
+ Log.v(
+ TAG,
+ "covered: $coveredPixels, sensor: $sensorPixels, " +
+ "percentage: $percentage, isCenterTouched: $isTargetTouched"
+ )
+ }
+
+ return percentage >= params.minOverlap && isTargetTouched
+ }
+
+ /** Checks if point is in the sensor center target circle, outer circle, or outside of sensor */
+ private fun isPartOfSensorArea(x: Int, y: Int, cX: Int, cY: Int, r: Int): SensorPixelPosition {
+ val dx = cX - x
+ val dy = cY - y
+
+ val disSquared = dx * dx + dy * dy
+
+ return if (disSquared <= (r * params.targetSize) * (r * params.targetSize)) {
+ SensorPixelPosition.TARGET
+ } else if (disSquared <= r * r) {
+ SensorPixelPosition.SENSOR
+ } else {
+ SensorPixelPosition.OUTSIDE
+ }
}
private fun checkPoint(point: Point, touchData: NormalizedTouchData): Boolean {
@@ -45,29 +107,9 @@
val c: Float = sin(touchData.orientation) * (point.x - touchData.x)
val d: Float = cos(touchData.orientation) * (point.y - touchData.y)
val result =
- (a + b).pow(2) / (touchData.minor / 2).pow(2) +
- (c - d).pow(2) / (touchData.major / 2).pow(2)
+ (a + b) * (a + b) / ((touchData.minor / 2) * (touchData.minor / 2)) +
+ (c - d) * (c - d) / ((touchData.major / 2) * (touchData.major / 2))
return result <= 1
}
-
- @VisibleForTesting
- fun calculateSensorPoints(sensorBounds: Rect): List<Point> {
- val sensorX = sensorBounds.centerX()
- val sensorY = sensorBounds.centerY()
- val cornerOffset: Int = sensorBounds.width() / 4
- val sideOffset: Int = sensorBounds.width() / 3
-
- return listOf(
- Point(sensorX - cornerOffset, sensorY - cornerOffset),
- Point(sensorX, sensorY - sideOffset),
- Point(sensorX + cornerOffset, sensorY - cornerOffset),
- Point(sensorX - sideOffset, sensorY),
- Point(sensorX, sensorY),
- Point(sensorX + sideOffset, sensorY),
- Point(sensorX - cornerOffset, sensorY + cornerOffset),
- Point(sensorX, sensorY + sideOffset),
- Point(sensorX + cornerOffset, sensorY + cornerOffset)
- )
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 1c26841..82bb723 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -21,6 +21,7 @@
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN;
+import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
@@ -35,6 +36,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -57,6 +59,7 @@
private final Provider<ClipboardOverlayController> mOverlayProvider;
private final ClipboardToast mClipboardToast;
private final ClipboardManager mClipboardManager;
+ private final FeatureFlags mFeatureFlags;
private final UiEventLogger mUiEventLogger;
private ClipboardOverlay mClipboardOverlay;
@@ -65,11 +68,13 @@
Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
ClipboardToast clipboardToast,
ClipboardManager clipboardManager,
+ FeatureFlags featureFlags,
UiEventLogger uiEventLogger) {
mContext = context;
mOverlayProvider = clipboardOverlayControllerProvider;
mClipboardToast = clipboardToast;
mClipboardManager = clipboardManager;
+ mFeatureFlags = featureFlags;
mUiEventLogger = uiEventLogger;
}
@@ -107,7 +112,11 @@
} else {
mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource);
}
- mClipboardOverlay.setClipData(clipData, clipSource);
+ if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
+ mClipboardOverlay.setClipData(clipData, clipSource);
+ } else {
+ mClipboardOverlay.setClipDataLegacy(clipData, clipSource);
+ }
mClipboardOverlay.setOnSessionCompleteListener(() -> {
// Session is complete, free memory until it's needed again.
mClipboardOverlay = null;
@@ -150,6 +159,8 @@
}
interface ClipboardOverlay {
+ void setClipDataLegacy(ClipData clipData, String clipSource);
+
void setClipData(ClipData clipData, String clipSource);
void setOnSessionCompleteListener(Runnable runnable);
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt
new file mode 100644
index 0000000..c7aaf09
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.clipboardoverlay
+
+import android.content.ClipData
+import android.content.ClipDescription.EXTRA_IS_SENSITIVE
+import android.content.Context
+import android.graphics.Bitmap
+import android.text.TextUtils
+import android.util.Log
+import android.util.Size
+import com.android.systemui.R
+import java.io.IOException
+
+data class ClipboardModel(
+ val clipData: ClipData?,
+ val source: String,
+ val type: Type = Type.OTHER,
+ val item: ClipData.Item? = null,
+ val isSensitive: Boolean = false,
+ val isRemote: Boolean = false,
+) {
+ private var _bitmap: Bitmap? = null
+
+ fun dataMatches(other: ClipboardModel?): Boolean {
+ if (other == null) {
+ return false
+ }
+ return source == other.source &&
+ type == other.type &&
+ item?.text == other.item?.text &&
+ item?.uri == other.item?.uri &&
+ isSensitive == other.isSensitive
+ }
+
+ fun loadThumbnail(context: Context): Bitmap? {
+ if (_bitmap == null && type == Type.IMAGE && item?.uri != null) {
+ try {
+ val size = context.resources.getDimensionPixelSize(R.dimen.overlay_x_scale)
+ _bitmap =
+ context.contentResolver.loadThumbnail(item.uri, Size(size, size * 4), null)
+ } catch (e: IOException) {
+ Log.e(TAG, "Thumbnail loading failed!", e)
+ }
+ }
+ return _bitmap
+ }
+
+ internal companion object {
+ private val TAG: String = "ClipboardModel"
+
+ @JvmStatic
+ fun fromClipData(
+ context: Context,
+ utils: ClipboardOverlayUtils,
+ clipData: ClipData?,
+ source: String
+ ): ClipboardModel {
+ if (clipData == null || clipData.itemCount == 0) {
+ return ClipboardModel(clipData, source)
+ }
+ val sensitive = clipData.description?.extras?.getBoolean(EXTRA_IS_SENSITIVE) ?: false
+ val item = clipData.getItemAt(0)!!
+ val type = getType(context, item)
+ val remote = utils.isRemoteCopy(context, clipData, source)
+ return ClipboardModel(clipData, source, type, item, sensitive, remote)
+ }
+
+ private fun getType(context: Context, item: ClipData.Item): Type {
+ return if (!TextUtils.isEmpty(item.text)) {
+ Type.TEXT
+ } else if (
+ item.uri != null &&
+ context.contentResolver.getType(item.uri)?.startsWith("image") == true
+ ) {
+ Type.IMAGE
+ } else {
+ Type.OTHER
+ }
+ }
+ }
+
+ enum class Type {
+ TEXT,
+ IMAGE,
+ OTHER
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 8c8ee8a..b41f308 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -17,7 +17,6 @@
package com.android.systemui.clipboardoverlay;
import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
-import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON;
@@ -31,10 +30,9 @@
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
+import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
import static com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR;
-import static java.util.Objects.requireNonNull;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.RemoteAction;
@@ -47,7 +45,6 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
-import android.hardware.display.DisplayManager;
import android.hardware.input.InputManager;
import android.net.Uri;
import android.os.Looper;
@@ -55,14 +52,15 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.Size;
-import android.view.Display;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputMonitor;
import android.view.MotionEvent;
+import android.view.WindowInsets;
import androidx.annotation.NonNull;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -71,7 +69,6 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.screenshot.TimeoutHandler;
-import com.android.systemui.settings.DisplayTracker;
import java.io.IOException;
import java.util.Optional;
@@ -95,8 +92,6 @@
private final Context mContext;
private final ClipboardLogger mClipboardLogger;
private final BroadcastDispatcher mBroadcastDispatcher;
- private final DisplayManager mDisplayManager;
- private final DisplayTracker mDisplayTracker;
private final ClipboardOverlayWindow mWindow;
private final TimeoutHandler mTimeoutHandler;
private final ClipboardOverlayUtils mClipboardUtils;
@@ -122,6 +117,9 @@
private Runnable mOnUiUpdate;
+ private boolean mIsMinimized;
+ private ClipboardModel mClipboardModel;
+
private final ClipboardOverlayView.ClipboardOverlayCallbacks mClipboardCallbacks =
new ClipboardOverlayView.ClipboardOverlayCallbacks() {
@Override
@@ -175,6 +173,13 @@
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
animateOut();
}
+
+ @Override
+ public void onMinimizedViewTapped() {
+ if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
+ animateFromMinimized();
+ }
+ }
};
@Inject
@@ -187,33 +192,28 @@
FeatureFlags featureFlags,
ClipboardOverlayUtils clipboardUtils,
@Background Executor bgExecutor,
- UiEventLogger uiEventLogger,
- DisplayTracker displayTracker) {
+ UiEventLogger uiEventLogger) {
+ mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
- mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
- mDisplayTracker = displayTracker;
- final Context displayContext = context.createDisplayContext(getDefaultDisplay());
- mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);
mClipboardLogger = new ClipboardLogger(uiEventLogger);
mView = clipboardOverlayView;
mWindow = clipboardOverlayWindow;
- mWindow.init(mView::setInsets, () -> {
+ mWindow.init(this::onInsetsChanged, () -> {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
hideImmediate();
});
+ mFeatureFlags = featureFlags;
mTimeoutHandler = timeoutHandler;
mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
- mFeatureFlags = featureFlags;
mClipboardUtils = clipboardUtils;
mBgExecutor = bgExecutor;
mView.setCallbacks(mClipboardCallbacks);
-
mWindow.withWindowAttached(() -> {
mWindow.setContentView(mView);
mView.setInsets(mWindow.getWindowInsets(),
@@ -258,8 +258,135 @@
broadcastSender.sendBroadcast(copyIntent, SELF_PERMISSION);
}
+ @VisibleForTesting
+ void onInsetsChanged(WindowInsets insets, int orientation) {
+ mView.setInsets(insets, orientation);
+ if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
+ if (shouldShowMinimized(insets) && !mIsMinimized) {
+ mIsMinimized = true;
+ mView.setMinimized(true);
+ }
+ }
+ }
+
@Override // ClipboardListener.ClipboardOverlay
- public void setClipData(ClipData clipData, String clipSource) {
+ public void setClipData(ClipData data, String source) {
+ ClipboardModel model = ClipboardModel.fromClipData(mContext, mClipboardUtils, data, source);
+ if (mExitAnimator != null && mExitAnimator.isRunning()) {
+ mExitAnimator.cancel();
+ }
+ boolean shouldAnimate = !model.dataMatches(mClipboardModel);
+ mClipboardModel = model;
+ mClipboardLogger.setClipSource(mClipboardModel.getSource());
+ if (shouldAnimate) {
+ reset();
+ mClipboardLogger.setClipSource(mClipboardModel.getSource());
+ if (shouldShowMinimized(mWindow.getWindowInsets())) {
+ mIsMinimized = true;
+ mView.setMinimized(true);
+ } else {
+ setExpandedView();
+ }
+ animateIn();
+ mView.announceForAccessibility(getAccessibilityAnnouncement(mClipboardModel.getType()));
+ } else if (!mIsMinimized) {
+ setExpandedView();
+ }
+ if (mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR) && mClipboardModel.isRemote()) {
+ mTimeoutHandler.cancelTimeout();
+ mOnUiUpdate = null;
+ } else {
+ mOnUiUpdate = mTimeoutHandler::resetTimeout;
+ mOnUiUpdate.run();
+ }
+ }
+
+ private void setExpandedView() {
+ final ClipboardModel model = mClipboardModel;
+ mView.setMinimized(false);
+ switch (model.getType()) {
+ case TEXT:
+ if ((mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR) && model.isRemote())
+ || DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
+ if (model.getItem().getTextLinks() != null) {
+ classifyText(model);
+ }
+ }
+ if (model.isSensitive()) {
+ mView.showTextPreview(mContext.getString(R.string.clipboard_asterisks), true);
+ } else {
+ mView.showTextPreview(model.getItem().getText(), false);
+ }
+ mView.setEditAccessibilityAction(true);
+ mOnPreviewTapped = this::editText;
+ break;
+ case IMAGE:
+ if (model.isSensitive() || model.loadThumbnail(mContext) != null) {
+ mView.showImagePreview(
+ model.isSensitive() ? null : model.loadThumbnail(mContext));
+ mView.setEditAccessibilityAction(true);
+ mOnPreviewTapped = () -> editImage(model.getItem().getUri());
+ } else {
+ // image loading failed
+ mView.showDefaultTextPreview();
+ }
+ break;
+ case OTHER:
+ mView.showDefaultTextPreview();
+ break;
+ }
+ if (mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR)) {
+ if (!model.isRemote()) {
+ maybeShowRemoteCopy(model.getClipData());
+ }
+ } else {
+ maybeShowRemoteCopy(model.getClipData());
+ }
+ if (model.getType() != ClipboardModel.Type.OTHER) {
+ mOnShareTapped = () -> shareContent(model.getClipData());
+ mView.showShareChip();
+ }
+ }
+
+ private boolean shouldShowMinimized(WindowInsets insets) {
+ return insets.getInsets(WindowInsets.Type.ime()).bottom > 0;
+ }
+
+ private void animateFromMinimized() {
+ mIsMinimized = false;
+ setExpandedView();
+ animateIn();
+ }
+
+ private String getAccessibilityAnnouncement(ClipboardModel.Type type) {
+ if (type == ClipboardModel.Type.TEXT) {
+ return mContext.getString(R.string.clipboard_text_copied);
+ } else if (type == ClipboardModel.Type.IMAGE) {
+ return mContext.getString(R.string.clipboard_image_copied);
+ } else {
+ return mContext.getString(R.string.clipboard_content_copied);
+ }
+ }
+
+ private void classifyText(ClipboardModel model) {
+ mBgExecutor.execute(() -> {
+ Optional<RemoteAction> remoteAction =
+ mClipboardUtils.getAction(model.getItem(), model.getSource());
+ if (model.equals(mClipboardModel)) {
+ remoteAction.ifPresent(action -> {
+ mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN);
+ mView.setActionChip(action, () -> {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
+ animateOut();
+ });
+ });
+ }
+ });
+ }
+
+ @Override // ClipboardListener.ClipboardOverlay
+ public void setClipDataLegacy(ClipData clipData, String clipSource) {
if (mExitAnimator != null && mExitAnimator.isRunning()) {
mExitAnimator.cancel();
}
@@ -516,10 +643,6 @@
mClipboardLogger.reset();
}
- private Display getDefaultDisplay() {
- return mDisplayManager.getDisplay(mDisplayTracker.getDefaultDisplayId());
- }
-
static class ClipboardLogger {
private final UiEventLogger mUiEventLogger;
private String mClipSource;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
index 2d33157..c9e01ce 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -18,8 +18,6 @@
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static java.util.Objects.requireNonNull;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -77,6 +75,8 @@
void onShareButtonTapped();
void onPreviewTapped();
+
+ void onMinimizedViewTapped();
}
private static final String TAG = "ClipboardView";
@@ -92,6 +92,7 @@
private ImageView mImagePreview;
private TextView mTextPreview;
private TextView mHiddenPreview;
+ private LinearLayout mMinimizedPreview;
private View mPreviewBorder;
private OverlayActionChip mEditChip;
private OverlayActionChip mShareChip;
@@ -117,18 +118,18 @@
@Override
protected void onFinishInflate() {
- mActionContainerBackground =
- requireNonNull(findViewById(R.id.actions_container_background));
- mActionContainer = requireNonNull(findViewById(R.id.actions));
- mClipboardPreview = requireNonNull(findViewById(R.id.clipboard_preview));
- mImagePreview = requireNonNull(findViewById(R.id.image_preview));
- mTextPreview = requireNonNull(findViewById(R.id.text_preview));
- mHiddenPreview = requireNonNull(findViewById(R.id.hidden_preview));
- mPreviewBorder = requireNonNull(findViewById(R.id.preview_border));
- mEditChip = requireNonNull(findViewById(R.id.edit_chip));
- mShareChip = requireNonNull(findViewById(R.id.share_chip));
- mRemoteCopyChip = requireNonNull(findViewById(R.id.remote_copy_chip));
- mDismissButton = requireNonNull(findViewById(R.id.dismiss_button));
+ mActionContainerBackground = requireViewById(R.id.actions_container_background);
+ mActionContainer = requireViewById(R.id.actions);
+ mClipboardPreview = requireViewById(R.id.clipboard_preview);
+ mPreviewBorder = requireViewById(R.id.preview_border);
+ mImagePreview = requireViewById(R.id.image_preview);
+ mTextPreview = requireViewById(R.id.text_preview);
+ mHiddenPreview = requireViewById(R.id.hidden_preview);
+ mMinimizedPreview = requireViewById(R.id.minimized_preview);
+ mEditChip = requireViewById(R.id.edit_chip);
+ mShareChip = requireViewById(R.id.share_chip);
+ mRemoteCopyChip = requireViewById(R.id.remote_copy_chip);
+ mDismissButton = requireViewById(R.id.dismiss_button);
mEditChip.setAlpha(1);
mShareChip.setAlpha(1);
@@ -163,6 +164,7 @@
mDismissButton.setOnClickListener(v -> clipboardCallbacks.onDismissButtonTapped());
mRemoteCopyChip.setOnClickListener(v -> clipboardCallbacks.onRemoteCopyButtonTapped());
mClipboardPreview.setOnClickListener(v -> clipboardCallbacks.onPreviewTapped());
+ mMinimizedPreview.setOnClickListener(v -> clipboardCallbacks.onMinimizedViewTapped());
}
void setEditAccessibilityAction(boolean editable) {
@@ -177,12 +179,28 @@
}
}
+ void setMinimized(boolean minimized) {
+ if (minimized) {
+ mMinimizedPreview.setVisibility(View.VISIBLE);
+ mClipboardPreview.setVisibility(View.GONE);
+ mPreviewBorder.setVisibility(View.GONE);
+ mActionContainer.setVisibility(View.GONE);
+ mActionContainerBackground.setVisibility(View.GONE);
+ } else {
+ mMinimizedPreview.setVisibility(View.GONE);
+ mClipboardPreview.setVisibility(View.VISIBLE);
+ mPreviewBorder.setVisibility(View.VISIBLE);
+ mActionContainer.setVisibility(View.VISIBLE);
+ }
+ }
+
void setInsets(WindowInsets insets, int orientation) {
FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) getLayoutParams();
if (p == null) {
return;
}
Rect margins = computeMargins(insets, orientation);
+
p.setMargins(margins.left, margins.top, margins.right, margins.bottom);
setLayoutParams(p);
requestLayout();
@@ -204,6 +222,12 @@
(int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
touchRegion.op(tmpRect, Region.Op.UNION);
+ mMinimizedPreview.getBoundsOnScreen(tmpRect);
+ tmpRect.inset(
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
+ touchRegion.op(tmpRect, Region.Op.UNION);
+
mDismissButton.getBoundsOnScreen(tmpRect);
touchRegion.op(tmpRect, Region.Op.UNION);
@@ -298,6 +322,8 @@
scaleAnim.setDuration(333);
scaleAnim.addUpdateListener(animation -> {
float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+ mMinimizedPreview.setScaleX(previewScale);
+ mMinimizedPreview.setScaleY(previewScale);
mClipboardPreview.setScaleX(previewScale);
mClipboardPreview.setScaleY(previewScale);
mPreviewBorder.setScaleX(previewScale);
@@ -319,12 +345,14 @@
alphaAnim.setDuration(283);
alphaAnim.addUpdateListener(animation -> {
float alpha = animation.getAnimatedFraction();
+ mMinimizedPreview.setAlpha(alpha);
mClipboardPreview.setAlpha(alpha);
mPreviewBorder.setAlpha(alpha);
mDismissButton.setAlpha(alpha);
mActionContainer.setAlpha(alpha);
});
+ mMinimizedPreview.setAlpha(0);
mActionContainer.setAlpha(0);
mPreviewBorder.setAlpha(0);
mClipboardPreview.setAlpha(0);
@@ -356,6 +384,8 @@
scaleAnim.setDuration(250);
scaleAnim.addUpdateListener(animation -> {
float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+ mMinimizedPreview.setScaleX(previewScale);
+ mMinimizedPreview.setScaleY(previewScale);
mClipboardPreview.setScaleX(previewScale);
mClipboardPreview.setScaleY(previewScale);
mPreviewBorder.setScaleX(previewScale);
@@ -377,6 +407,7 @@
alphaAnim.setDuration(166);
alphaAnim.addUpdateListener(animation -> {
float alpha = 1 - animation.getAnimatedFraction();
+ mMinimizedPreview.setAlpha(alpha);
mClipboardPreview.setAlpha(alpha);
mPreviewBorder.setAlpha(alpha);
mDismissButton.setAlpha(alpha);
@@ -399,6 +430,7 @@
mTextPreview.setVisibility(View.GONE);
mImagePreview.setVisibility(View.GONE);
mHiddenPreview.setVisibility(View.GONE);
+ mMinimizedPreview.setVisibility(View.GONE);
v.setVisibility(View.VISIBLE);
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
new file mode 100644
index 0000000..ae6a08f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
@@ -0,0 +1,185 @@
+/*
+ * 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.common.ui.view;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+
+import com.android.systemui.R;
+
+/**
+ * The layout contains a seekbar whose progress could be modified
+ * through the icons on two ends of the seekbar.
+ */
+public class SeekBarWithIconButtonsView extends LinearLayout {
+
+ private static final int DEFAULT_SEEKBAR_MAX = 6;
+ private static final int DEFAULT_SEEKBAR_PROGRESS = 0;
+
+ private ImageView mIconStart;
+ private ImageView mIconEnd;
+ private SeekBar mSeekbar;
+
+ private SeekBarChangeListener mSeekBarListener = new SeekBarChangeListener();
+
+ public SeekBarWithIconButtonsView(Context context) {
+ this(context, null);
+ }
+
+ public SeekBarWithIconButtonsView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SeekBarWithIconButtonsView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public SeekBarWithIconButtonsView(Context context,
+ AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ LayoutInflater.from(context).inflate(
+ R.layout.seekbar_with_icon_buttons, this, /* attachToRoot= */ true);
+
+ mIconStart = findViewById(R.id.icon_start);
+ mIconEnd = findViewById(R.id.icon_end);
+ mSeekbar = findViewById(R.id.seekbar);
+
+ if (attrs != null) {
+ TypedArray typedArray = context.obtainStyledAttributes(
+ attrs,
+ R.styleable.SeekBarWithIconButtonsView_Layout,
+ defStyleAttr, defStyleRes
+ );
+ int max = typedArray.getInt(
+ R.styleable.SeekBarWithIconButtonsView_Layout_max, DEFAULT_SEEKBAR_MAX);
+ int progress = typedArray.getInt(
+ R.styleable.SeekBarWithIconButtonsView_Layout_progress,
+ DEFAULT_SEEKBAR_PROGRESS);
+ mSeekbar.setMax(max);
+ setProgress(progress);
+
+ int iconStartContentDescriptionId = typedArray.getResourceId(
+ R.styleable.SeekBarWithIconButtonsView_Layout_iconStartContentDescription,
+ /* defValue= */ 0);
+ int iconEndContentDescriptionId = typedArray.getResourceId(
+ R.styleable.SeekBarWithIconButtonsView_Layout_iconEndContentDescription,
+ /* defValue= */ 0);
+ if (iconStartContentDescriptionId != 0) {
+ final String contentDescription =
+ context.getString(iconStartContentDescriptionId);
+ mIconStart.setContentDescription(contentDescription);
+ }
+ if (iconEndContentDescriptionId != 0) {
+ final String contentDescription =
+ context.getString(iconEndContentDescriptionId);
+ mIconEnd.setContentDescription(contentDescription);
+ }
+
+ typedArray.recycle();
+ } else {
+ mSeekbar.setMax(DEFAULT_SEEKBAR_MAX);
+ setProgress(DEFAULT_SEEKBAR_PROGRESS);
+ }
+
+ mSeekbar.setOnSeekBarChangeListener(mSeekBarListener);
+
+ mIconStart.setOnClickListener((view) -> {
+ final int progress = mSeekbar.getProgress();
+ if (progress > 0) {
+ mSeekbar.setProgress(progress - 1);
+ setIconViewEnabled(mIconStart, mSeekbar.getProgress() > 0);
+ }
+ });
+
+ mIconEnd.setOnClickListener((view) -> {
+ final int progress = mSeekbar.getProgress();
+ if (progress < mSeekbar.getMax()) {
+ mSeekbar.setProgress(progress + 1);
+ setIconViewEnabled(mIconEnd, mSeekbar.getProgress() < mSeekbar.getMax());
+ }
+ });
+ }
+
+ private static void setIconViewEnabled(View iconView, boolean enabled) {
+ iconView.setEnabled(enabled);
+ }
+
+ /**
+ * Sets a onSeekbarChangeListener to the seekbar in the layout.
+ * We update the Start Icon and End Icon if needed when the seekbar progress is changed.
+ */
+ public void setOnSeekBarChangeListener(
+ @Nullable SeekBar.OnSeekBarChangeListener onSeekBarChangeListener) {
+ mSeekBarListener.setOnSeekBarChangeListener(onSeekBarChangeListener);
+ }
+
+ /**
+ * Start and End icons might need to be updated when there is a change in seekbar progress.
+ * Icon Start will need to be enabled when the seekbar progress is larger than 0.
+ * Icon End will need to be enabled when the seekbar progress is less than Max.
+ */
+ private void updateIconViewIfNeeded(int progress) {
+ setIconViewEnabled(mIconStart, progress > 0);
+ setIconViewEnabled(mIconEnd, progress < mSeekbar.getMax());
+ }
+
+ /**
+ * Sets progress to the seekbar in the layout.
+ */
+ public void setProgress(int progress) {
+ mSeekbar.setProgress(progress);
+ updateIconViewIfNeeded(progress);
+ }
+
+ private class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
+ private SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener = null;
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (mOnSeekBarChangeListener != null) {
+ mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
+ }
+ updateIconViewIfNeeded(progress);
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ if (mOnSeekBarChangeListener != null) {
+ mOnSeekBarChangeListener.onStartTrackingTouch(seekBar);
+ }
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ if (mOnSeekBarChangeListener != null) {
+ mOnSeekBarChangeListener.onStopTrackingTouch(seekBar);
+ }
+ }
+
+ void setOnSeekBarChangeListener(SeekBar.OnSeekBarChangeListener listener) {
+ mOnSeekBarChangeListener = listener;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
index dbe301d..860149d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
@@ -28,12 +28,13 @@
import android.content.pm.ServiceInfo
import android.os.UserHandle
import android.service.controls.ControlsProviderService
+import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import com.android.settingslib.applications.DefaultAppInfo
import com.android.systemui.R
import java.util.Objects
-class ControlsServiceInfo(
+open class ControlsServiceInfo(
private val context: Context,
val serviceInfo: ServiceInfo
) : DefaultAppInfo(
@@ -64,7 +65,7 @@
* [R.array.config_controlsPreferredPackages] can declare activities for use as a panel.
*/
var panelActivity: ComponentName? = null
- private set
+ protected set
private var resolved: Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index ce82af7..700f4f6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -123,16 +123,13 @@
userChanging = false
}
- private val userTrackerCallback = object : UserTracker.Callback {
- override fun onUserChanged(newUser: Int, userContext: Context) {
- userChanging = true
- val newUserHandle = UserHandle.of(newUser)
- if (currentUser == newUserHandle) {
- userChanging = false
- return
- }
- setValuesForUser(newUserHandle)
+ override fun changeUser(newUser: UserHandle) {
+ userChanging = true
+ if (currentUser == newUser) {
+ userChanging = false
+ return
}
+ setValuesForUser(newUser)
}
@VisibleForTesting
@@ -233,7 +230,6 @@
dumpManager.registerDumpable(javaClass.name, this)
resetFavorites()
userChanging = false
- userTracker.addCallback(userTrackerCallback, executor)
context.registerReceiver(
restoreFinishedReceiver,
IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
@@ -245,7 +241,6 @@
}
fun destroy() {
- userTracker.removeCallback(userTrackerCallback)
context.unregisterReceiver(restoreFinishedReceiver)
listingController.removeCallback(listingCallback)
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/StartControlsStartableModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/StartControlsStartableModule.kt
new file mode 100644
index 0000000..3f20c26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/StartControlsStartableModule.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.controls.dagger
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.controls.start.ControlsStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+abstract class StartControlsStartableModule {
+ @Binds
+ @IntoMap
+ @ClassKey(ControlsStartable::class)
+ abstract fun bindFeature(impl: ControlsStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
new file mode 100644
index 0000000..9d99253
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
@@ -0,0 +1,119 @@
+/*
+ * 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.controls.start
+
+import android.content.Context
+import android.content.res.Resources
+import android.os.UserHandle
+import com.android.systemui.CoreStartable
+import com.android.systemui.R
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.SelectedItem
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Started with SystemUI to perform early operations for device controls subsystem (only if enabled)
+ *
+ * In particular, it will perform the following:
+ * * If there is no preferred selection for provider and at least one of the preferred packages
+ * provides a panel, it will select the first one that does.
+ * * If the preferred selection provides a panel, it will bind to that service (to reduce latency on
+ * displaying the panel).
+ *
+ * It will also perform those operations on user change.
+ */
+@SysUISingleton
+class ControlsStartable
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ @Background private val executor: Executor,
+ private val controlsComponent: ControlsComponent,
+ private val userTracker: UserTracker
+) : CoreStartable {
+
+ // These two controllers can only be accessed after `start` method once we've checked if the
+ // feature is enabled
+ private val controlsController: ControlsController
+ get() = controlsComponent.getControlsController().get()
+
+ private val controlsListingController: ControlsListingController
+ get() = controlsComponent.getControlsListingController().get()
+
+ private val userTrackerCallback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ controlsController.changeUser(UserHandle.of(newUser))
+ startForUser()
+ }
+ }
+
+ override fun start() {
+ if (!controlsComponent.isEnabled()) {
+ // Controls is disabled, we don't need this anymore
+ return
+ }
+ startForUser()
+ userTracker.addCallback(userTrackerCallback, executor)
+ }
+
+ private fun startForUser() {
+ selectDefaultPanelIfNecessary()
+ bindToPanel()
+ }
+
+ private fun selectDefaultPanelIfNecessary() {
+ val currentSelection = controlsController.getPreferredSelection()
+ if (currentSelection == SelectedItem.EMPTY_SELECTION) {
+ val availableServices = controlsListingController.getCurrentServices()
+ val panels = availableServices.filter { it.panelActivity != null }
+ resources
+ .getStringArray(R.array.config_controlsPreferredPackages)
+ // Looking for the first element in the string array such that there is one package
+ // that has a panel. It will return null if there are no packages in the array,
+ // or if no packages in the array have a panel associated with it.
+ .firstNotNullOfOrNull { name ->
+ panels.firstOrNull { it.componentName.packageName == name }
+ }
+ ?.let { info ->
+ controlsController.setPreferredSelection(
+ SelectedItem.PanelItem(info.loadLabel(), info.componentName)
+ )
+ }
+ }
+ }
+
+ private fun bindToPanel() {
+ val currentSelection = controlsController.getPreferredSelection()
+ val panels =
+ controlsListingController.getCurrentServices().filter { it.panelActivity != null }
+ if (
+ currentSelection is SelectedItem.PanelItem &&
+ panels.firstOrNull { it.componentName == currentSelection.componentName } != null
+ ) {
+ controlsController.bindComponentForPanel(currentSelection.componentName)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 9e71bef..58f4835 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -28,6 +28,7 @@
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
+import android.os.Trace
import android.service.controls.Control
import android.service.controls.ControlsProviderService
import android.util.Log
@@ -224,6 +225,7 @@
activityContext: Context
) {
Log.d(ControlsUiController.TAG, "show()")
+ Trace.instant(Trace.TRACE_TAG_APP, "ControlsUiControllerImpl#show")
this.parent = parent
this.onDismiss = onDismiss
this.activityContext = activityContext
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
index f5764c2..3b6ab20 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
@@ -27,6 +27,7 @@
import android.graphics.Color
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.RoundRectShape
+import android.os.Trace
import com.android.systemui.R
import com.android.systemui.util.boundsOnScreen
import com.android.wm.shell.TaskView
@@ -84,6 +85,7 @@
options,
taskView.boundsOnScreen
)
+ Trace.instant(Trace.TRACE_TAG_APP, "PanelTaskViewController - startActivity")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index 4eb444e..a5beb4e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -23,6 +23,8 @@
import com.android.systemui.keyguard.WorkLockActivity;
import com.android.systemui.people.PeopleSpaceActivity;
import com.android.systemui.people.widget.LaunchConversationActivity;
+import com.android.systemui.screenshot.AppClipsActivity;
+import com.android.systemui.screenshot.AppClipsTrampolineActivity;
import com.android.systemui.screenshot.LongScreenshotActivity;
import com.android.systemui.sensorprivacy.SensorUseStartedActivity;
import com.android.systemui.sensorprivacy.television.TvSensorPrivacyChangedActivity;
@@ -119,6 +121,18 @@
@ClassKey(LongScreenshotActivity.class)
public abstract Activity bindLongScreenshotActivity(LongScreenshotActivity activity);
+ /** Inject into AppClipsTrampolineActivity. */
+ @Binds
+ @IntoMap
+ @ClassKey(AppClipsTrampolineActivity.class)
+ public abstract Activity bindAppClipsTrampolineActivity(AppClipsTrampolineActivity activity);
+
+ /** Inject into AppClipsActivity. */
+ @Binds
+ @IntoMap
+ @ClassKey(AppClipsActivity.class)
+ public abstract Activity bindAppClipsActivity(AppClipsActivity activity);
+
/** Inject into LaunchConversationActivity. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 48f90bc..b5a1f45 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -27,7 +27,9 @@
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.UdfpsOverlay
import com.android.systemui.clipboardoverlay.ClipboardListener
+import com.android.systemui.controls.dagger.StartControlsStartableModule
import com.android.systemui.dagger.qualifiers.PerUser
+import com.android.systemui.dreams.AssistantAttentionMonitor
import com.android.systemui.dreams.DreamMonitor
import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.keyboard.KeyboardUI
@@ -66,7 +68,10 @@
/**
* Collection of {@link CoreStartable}s that should be run on AOSP.
*/
-@Module(includes = [MultiUserUtilsModule::class])
+@Module(includes = [
+ MultiUserUtilsModule::class,
+ StartControlsStartableModule::class
+])
abstract class SystemUICoreStartableModule {
/** Inject into AuthController. */
@Binds
@@ -309,4 +314,10 @@
@IntoMap
@ClassKey(DreamMonitor::class)
abstract fun bindDreamMonitor(sysui: DreamMonitor): CoreStartable
+
+ /**Inject into AssistantAttentionMonitor */
+ @Binds
+ @IntoMap
+ @ClassKey(AssistantAttentionMonitor::class)
+ abstract fun bindAssistantAttentionMonitor(sysui: AssistantAttentionMonitor): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index c3f24f0..45872f4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -218,6 +218,10 @@
abstract BcSmartspaceConfigPlugin optionalBcSmartspaceConfigPlugin();
@BindsOptionalOf
+ @Named(SmartspaceModule.DATE_SMARTSPACE_DATA_PLUGIN)
+ abstract BcSmartspaceDataPlugin optionalDateSmartspaceConfigPlugin();
+
+ @BindsOptionalOf
@Named(SmartspaceModule.WEATHER_SMARTSPACE_DATA_PLUGIN)
abstract BcSmartspaceDataPlugin optionalWeatherSmartspaceConfigPlugin();
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index 976afd4..88c0c50 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -34,6 +34,7 @@
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -45,6 +46,7 @@
private val statusBarStateController: StatusBarStateController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@Main private val mainExecutor: Executor,
+ private val logger: ScreenDecorationsLogger,
) : DecorProviderFactory() {
private val display = context.display
private val displayInfo = DisplayInfo()
@@ -82,7 +84,8 @@
authController,
statusBarStateController,
keyguardUpdateMonitor,
- mainExecutor
+ mainExecutor,
+ logger,
)
)
}
@@ -104,7 +107,8 @@
private val authController: AuthController,
private val statusBarStateController: StatusBarStateController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val mainExecutor: Executor
+ private val mainExecutor: Executor,
+ private val logger: ScreenDecorationsLogger,
) : BoundDecorProvider() {
override val viewId: Int = com.android.systemui.R.id.face_scanning_anim
@@ -136,7 +140,8 @@
alignedBound,
statusBarStateController,
keyguardUpdateMonitor,
- mainExecutor
+ mainExecutor,
+ logger,
)
view.id = viewId
view.setColor(tintColor)
@@ -155,8 +160,9 @@
layoutParams.let { lp ->
lp.width = ViewGroup.LayoutParams.MATCH_PARENT
lp.height = ViewGroup.LayoutParams.MATCH_PARENT
+ logger.faceSensorLocation(authController.faceSensorLocation)
authController.faceSensorLocation?.y?.let { faceAuthSensorHeight ->
- val faceScanningHeight = (faceAuthSensorHeight * 2).toInt()
+ val faceScanningHeight = (faceAuthSensorHeight * 2)
when (rotation) {
Surface.ROTATION_0, Surface.ROTATION_180 ->
lp.height = faceScanningHeight
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/AssistantAttentionMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/AssistantAttentionMonitor.java
new file mode 100644
index 0000000..49d7f78
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/AssistantAttentionMonitor.java
@@ -0,0 +1,59 @@
+/*
+ * 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.dreams;
+
+import android.util.Log;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dreams.callbacks.AssistantAttentionCallback;
+import com.android.systemui.dreams.conditions.AssistantAttentionCondition;
+import com.android.systemui.shared.condition.Monitor;
+
+import javax.inject.Inject;
+
+/**
+ * A {@link CoreStartable} to retain a monitor for tracking assistant attention.
+ */
+public class AssistantAttentionMonitor implements CoreStartable {
+ private static final String TAG = "AssistAttentionMonitor";
+
+ // We retain a reference to the monitor so it is not garbage-collected.
+ private final Monitor mConditionMonitor;
+ private final AssistantAttentionCondition mAssistantAttentionCondition;
+ private final AssistantAttentionCallback mCallback;
+
+ @Inject
+ public AssistantAttentionMonitor(
+ Monitor monitor,
+ AssistantAttentionCondition assistantAttentionCondition,
+ AssistantAttentionCallback callback) {
+ mConditionMonitor = monitor;
+ mAssistantAttentionCondition = assistantAttentionCondition;
+ mCallback = callback;
+
+ }
+ @Override
+ public void start() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "started");
+ }
+
+ mConditionMonitor.addSubscription(new Monitor.Subscription.Builder(mCallback)
+ .addCondition(mAssistantAttentionCondition)
+ .build());
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index 2c7ecb1..b7f6a70 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -56,6 +56,8 @@
public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1;
public static final int STATE_DREAM_ENTRY_ANIMATIONS_FINISHED = 1 << 2;
public static final int STATE_DREAM_EXIT_ANIMATIONS_RUNNING = 1 << 3;
+ public static final int STATE_HAS_ASSISTANT_ATTENTION = 1 << 4;
+ public static final int STATE_DREAM_OVERLAY_STATUS_BAR_VISIBLE = 1 << 5;
private static final int OP_CLEAR_STATE = 1;
private static final int OP_SET_STATE = 2;
@@ -251,6 +253,22 @@
return containsState(STATE_DREAM_EXIT_ANIMATIONS_RUNNING);
}
+ /**
+ * Returns whether assistant currently has the user's attention.
+ * @return {@code true} if assistant has the user's attention, {@code false} otherwise.
+ */
+ public boolean hasAssistantAttention() {
+ return containsState(STATE_HAS_ASSISTANT_ATTENTION);
+ }
+
+ /**
+ * Returns whether the dream overlay status bar is currently visible.
+ * @return {@code true} if the status bar is visible, {@code false} otherwise.
+ */
+ public boolean isDreamOverlayStatusBarVisible() {
+ return containsState(STATE_DREAM_OVERLAY_STATUS_BAR_VISIBLE);
+ }
+
private boolean containsState(int state) {
return (mState & state) != 0;
}
@@ -310,6 +328,23 @@
}
/**
+ * Sets whether assistant currently has the user's attention.
+ * @param hasAttention {@code true} if has the user's attention, {@code false} otherwise.
+ */
+ public void setHasAssistantAttention(boolean hasAttention) {
+ modifyState(hasAttention ? OP_SET_STATE : OP_CLEAR_STATE, STATE_HAS_ASSISTANT_ATTENTION);
+ }
+
+ /**
+ * Sets whether the dream overlay status bar is visible.
+ * @param visible {@code true} if the status bar is visible, {@code false} otherwise.
+ */
+ public void setDreamOverlayStatusBarVisible(boolean visible) {
+ modifyState(
+ visible ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_STATUS_BAR_VISIBLE);
+ }
+
+ /**
* Returns the available complication types.
*/
@Complication.ComplicationType
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
index 96bce4c..7c1bfed 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
@@ -52,7 +52,8 @@
STATUS_ICON_CAMERA_DISABLED,
STATUS_ICON_MIC_DISABLED,
STATUS_ICON_MIC_CAMERA_DISABLED,
- STATUS_ICON_PRIORITY_MODE_ON
+ STATUS_ICON_PRIORITY_MODE_ON,
+ STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE,
})
public @interface StatusIconType {}
public static final int STATUS_ICON_NOTIFICATIONS = 0;
@@ -62,6 +63,7 @@
public static final int STATUS_ICON_MIC_DISABLED = 4;
public static final int STATUS_ICON_MIC_CAMERA_DISABLED = 5;
public static final int STATUS_ICON_PRIORITY_MODE_ON = 6;
+ public static final int STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE = 7;
private final Map<Integer, View> mStatusIcons = new HashMap<>();
private Context mContext;
@@ -132,6 +134,8 @@
fetchStatusIconForResId(R.id.dream_overlay_notification_indicator));
mStatusIcons.put(STATUS_ICON_PRIORITY_MODE_ON,
addDoubleShadow(fetchStatusIconForResId(R.id.dream_overlay_priority_mode)));
+ mStatusIcons.put(STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE,
+ fetchStatusIconForResId(R.id.dream_overlay_assistant_attention_indicator));
mSystemStatusViewGroup = findViewById(R.id.dream_overlay_system_status);
mExtraSystemStatusViewGroup = findViewById(R.id.dream_overlay_extra_items);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index 90c440c..2221a04 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -113,6 +113,7 @@
mEntryAnimationsFinished =
mDreamOverlayStateController.areEntryAnimationsFinished();
updateVisibility();
+ updateAssistantAttentionIcon();
}
};
@@ -214,6 +215,7 @@
provider -> provider.removeCallback(mNotificationCountCallback));
mStatusBarItemsProvider.removeCallback(mStatusBarItemsProviderCallback);
mView.removeAllExtraStatusBarItemViews();
+ mDreamOverlayStateController.setDreamOverlayStatusBarVisible(false);
mDreamOverlayStateController.removeCallback(mDreamOverlayStateCallback);
mTouchInsetSession.clear();
@@ -270,12 +272,20 @@
hasAlarm ? buildAlarmContentDescription(alarm) : null);
}
+ private void updateAssistantAttentionIcon() {
+ showIcon(DreamOverlayStatusBarView.STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE,
+ mDreamOverlayStateController.hasAssistantAttention());
+ }
+
private void updateVisibility() {
- if (shouldShowStatusBar()) {
- mView.setVisibility(View.VISIBLE);
- } else {
- mView.setVisibility(View.INVISIBLE);
+ final int currentVisibility = mView.getVisibility();
+ final int newVisibility = shouldShowStatusBar() ? View.VISIBLE : View.INVISIBLE;
+ if (currentVisibility == newVisibility) {
+ return;
}
+
+ mView.setVisibility(newVisibility);
+ mDreamOverlayStateController.setDreamOverlayStatusBarVisible(newVisibility == View.VISIBLE);
}
private String buildAlarmContentDescription(AlarmManager.AlarmClockInfo alarm) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/callbacks/AssistantAttentionCallback.java b/packages/SystemUI/src/com/android/systemui/dreams/callbacks/AssistantAttentionCallback.java
new file mode 100644
index 0000000..5d8a9b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/callbacks/AssistantAttentionCallback.java
@@ -0,0 +1,48 @@
+/*
+ * 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.dreams.callbacks;
+
+import android.util.Log;
+
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.shared.condition.Monitor;
+
+import javax.inject.Inject;
+
+/**
+ * A callback that informs {@link DreamOverlayStateController} when assistant has the user's
+ * attention.
+ */
+public class AssistantAttentionCallback implements Monitor.Callback {
+ private static final String TAG = "AssistAttentionCallback";
+
+ private final DreamOverlayStateController mStateController;
+
+ @Inject
+ public AssistantAttentionCallback(DreamOverlayStateController stateController) {
+ mStateController = stateController;
+ }
+
+ @Override
+ public void onConditionsChanged(boolean allConditionsMet) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onConditionChanged:" + allConditionsMet);
+ }
+
+ mStateController.setHasAssistantAttention(allConditionsMet);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
new file mode 100644
index 0000000..250cfec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.conditions;
+
+import com.android.internal.app.AssistUtils;
+import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.shared.condition.Condition;
+
+import javax.inject.Inject;
+
+/**
+ * {@link AssistantAttentionCondition} provides a signal when assistant has the user's attention.
+ */
+public class AssistantAttentionCondition extends Condition {
+ private final DreamOverlayStateController mDreamOverlayStateController;
+ private final AssistUtils mAssistUtils;
+ private boolean mEnabled;
+
+ private final IVisualQueryDetectionAttentionListener mVisualQueryDetectionAttentionListener =
+ new IVisualQueryDetectionAttentionListener.Stub() {
+ @Override
+ public void onAttentionGained() {
+ updateCondition(true);
+ }
+
+ @Override
+ public void onAttentionLost() {
+ updateCondition(false);
+ }
+ };
+
+ private final DreamOverlayStateController.Callback mCallback =
+ new DreamOverlayStateController.Callback() {
+ @Override
+ public void onStateChanged() {
+ if (mDreamOverlayStateController.isDreamOverlayStatusBarVisible()) {
+ enableVisualQueryDetection();
+ } else {
+ disableVisualQueryDetection();
+ }
+ }
+ };
+
+ @Inject
+ public AssistantAttentionCondition(
+ DreamOverlayStateController dreamOverlayStateController,
+ AssistUtils assistUtils) {
+ mDreamOverlayStateController = dreamOverlayStateController;
+ mAssistUtils = assistUtils;
+ }
+
+ @Override
+ protected void start() {
+ mDreamOverlayStateController.addCallback(mCallback);
+ }
+
+ @Override
+ protected void stop() {
+ disableVisualQueryDetection();
+ mDreamOverlayStateController.removeCallback(mCallback);
+ }
+
+ private void enableVisualQueryDetection() {
+ if (mEnabled) {
+ return;
+ }
+ mEnabled = true;
+ mAssistUtils.enableVisualQueryDetection(mVisualQueryDetectionAttentionListener);
+ }
+
+ private void disableVisualQueryDetection() {
+ if (!mEnabled) {
+ return;
+ }
+ mEnabled = false;
+ mAssistUtils.disableVisualQueryDetection();
+ // Make sure the condition is set to false as well.
+ updateCondition(false);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 7ff27cf..e1aed65 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -125,8 +125,7 @@
resourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher, "bouncer_user_switcher")
// TODO(b/254512676): Tracking Bug
- @JvmField
- val LOCKSCREEN_CUSTOM_CLOCKS = unreleasedFlag(207, "lockscreen_custom_clocks", teamfood = true)
+ @JvmField val LOCKSCREEN_CUSTOM_CLOCKS = releasedFlag(207, "lockscreen_custom_clocks")
/**
* Whether the clock on a wide lock screen should use the new "stepping" animation for moving
@@ -235,7 +234,7 @@
// TODO(b/258517050): Clean up after the feature is launched.
@JvmField
val SMARTSPACE_DATE_WEATHER_DECOUPLED =
- sysPropBooleanFlag(403, "persist.sysui.ss.dw_decoupled", default = false)
+ sysPropBooleanFlag(403, "persist.sysui.ss.dw_decoupled", default = true)
// 500 - quick settings
@@ -266,11 +265,10 @@
// 600- status bar
// TODO(b/256614753): Tracking Bug
- val NEW_STATUS_BAR_MOBILE_ICONS =
- unreleasedFlag(606, "new_status_bar_mobile_icons", teamfood = true)
+ val NEW_STATUS_BAR_MOBILE_ICONS = releasedFlag(606, "new_status_bar_mobile_icons")
// TODO(b/256614210): Tracking Bug
- val NEW_STATUS_BAR_WIFI_ICON = unreleasedFlag(607, "new_status_bar_wifi_icon", teamfood = true)
+ val NEW_STATUS_BAR_WIFI_ICON = releasedFlag(607, "new_status_bar_wifi_icon")
// TODO(b/256614751): Tracking Bug
val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND =
@@ -311,9 +309,7 @@
val SCREEN_CONTENTS_TRANSLATION = unreleasedFlag(803, "screen_contents_translation")
// 804 - monochromatic themes
- @JvmField
- val MONOCHROMATIC_THEMES =
- sysPropBooleanFlag(804, "persist.sysui.monochromatic", default = false)
+ @JvmField val MONOCHROMATIC_THEME = releasedFlag(804, "monochromatic")
// 900 - media
// TODO(b/254512697): Tracking Bug
@@ -335,30 +331,36 @@
@JvmField val DREAM_MEDIA_TAP_TO_OPEN = unreleasedFlag(906, "dream_media_tap_to_open")
// TODO(b/254513168): Tracking Bug
- @JvmField val UMO_SURFACE_RIPPLE = unreleasedFlag(907, "umo_surface_ripple")
+ @JvmField val UMO_SURFACE_RIPPLE = releasedFlag(907, "umo_surface_ripple")
@JvmField val MEDIA_FALSING_PENALTY = releasedFlag(908, "media_falsing_media")
// TODO(b/261734857): Tracking Bug
- @JvmField val UMO_TURBULENCE_NOISE = unreleasedFlag(909, "umo_turbulence_noise")
+ @JvmField val UMO_TURBULENCE_NOISE = releasedFlag(909, "umo_turbulence_noise")
// TODO(b/263272731): Tracking Bug
val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE =
unreleasedFlag(910, "media_ttt_receiver_success_ripple", teamfood = true)
// TODO(b/263512203): Tracking Bug
- val MEDIA_EXPLICIT_INDICATOR = unreleasedFlag(911, "media_explicit_indicator", teamfood = true)
+ val MEDIA_EXPLICIT_INDICATOR = releasedFlag(911, "media_explicit_indicator")
// TODO(b/265813373): Tracking Bug
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")
+ val MEDIA_RETAIN_SESSIONS = releasedFlag(913, "media_retain_sessions")
// TODO(b/266739309): Tracking Bug
@JvmField
- val MEDIA_RECOMMENDATION_CARD_UPDATE = unreleasedFlag(914, "media_recommendation_card_update")
+ val MEDIA_RECOMMENDATION_CARD_UPDATE = releasedFlag(914, "media_recommendation_card_update")
+
+ // TODO(b/267007629): Tracking Bug
+ val MEDIA_RESUME_PROGRESS = releasedFlag(915, "media_resume_progress")
+
+ // TODO(b/267166152) : Tracking Bug
+ val MEDIA_RETAIN_RECOMMENDATIONS = releasedFlag(916, "media_retain_recommendations")
// 1000 - dock
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
@@ -514,6 +516,9 @@
// TODO(b/266955521): Tracking bug
@JvmField val SCREENSHOT_DETECTION = unreleasedFlag(1303, "screenshot_detection")
+ // TODO(b/251205791): Tracking Bug
+ @JvmField val SCREENSHOT_APP_CLIPS = releasedFlag(1304, "screenshot_app_clips")
+
// 1400 - columbus
// TODO(b/254512756): Tracking Bug
val QUICK_TAP_IN_PCC = releasedFlag(1400, "quick_tap_in_pcc")
@@ -535,14 +540,10 @@
// TODO(b/266983474) Tracking Bug
val SHARESHEET_IMAGE_AND_TEXT_PREVIEW = unreleasedFlag(1503, "sharesheet_image_text_preview")
- // 1600 - accessibility
- // TODO(b/262224538): Tracking Bug
- @JvmField
- val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS =
- releasedFlag(1600, "a11y_floating_menu_fling_spring_animations")
-
// 1700 - clipboard
@JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
+ // TODO(b/267162944): Tracking bug
+ @JvmField val CLIPBOARD_MINIMIZED_LAYOUT = unreleasedFlag(1702, "clipboard_data_model")
// 1800 - shade container
@JvmField
@@ -551,7 +552,7 @@
@JvmField val DUAL_SHADE = releasedFlag(1801, "dual_shade")
// 1900
- @JvmField val NOTE_TASKS = unreleasedFlag(1900, "keycode_flag")
+ @JvmField val NOTE_TASKS = releasedFlag(1900, "keycode_flag")
// 2000 - device controls
@Keep @JvmField val USE_APP_PANELS = releasedFlag(2000, "use_app_panels", teamfood = true)
@@ -562,22 +563,25 @@
@JvmField
val CONTROLS_MANAGEMENT_NEW_FLOWS =
- unreleasedFlag(2002, "controls_management_new_flows", teamfood = true)
+ releasedFlag(2002, "controls_management_new_flows", teamfood = true)
// 2100 - Falsing Manager
@JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps")
// 2200 - udfps
// TODO(b/259264861): Tracking Bug
- @JvmField val UDFPS_NEW_TOUCH_DETECTION = unreleasedFlag(2200, "udfps_new_touch_detection")
- @JvmField val UDFPS_ELLIPSE_DEBUG_UI = unreleasedFlag(2201, "udfps_ellipse_debug")
- @JvmField val UDFPS_ELLIPSE_DETECTION = unreleasedFlag(2202, "udfps_ellipse_detection")
+ @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag(2200, "udfps_new_touch_detection")
+ @JvmField val UDFPS_ELLIPSE_DETECTION = releasedFlag(2201, "udfps_ellipse_detection")
// 2300 - stylus
- @JvmField val TRACK_STYLUS_EVER_USED = unreleasedFlag(2300, "track_stylus_ever_used")
- @JvmField val ENABLE_STYLUS_CHARGING_UI = unreleasedFlag(2301, "enable_stylus_charging_ui")
@JvmField
- val ENABLE_USI_BATTERY_NOTIFICATIONS = unreleasedFlag(2302, "enable_usi_battery_notifications")
+ val TRACK_STYLUS_EVER_USED = unreleasedFlag(2300, "track_stylus_ever_used", teamfood = true)
+ @JvmField
+ val ENABLE_STYLUS_CHARGING_UI =
+ unreleasedFlag(2301, "enable_stylus_charging_ui", teamfood = true)
+ @JvmField
+ val ENABLE_USI_BATTERY_NOTIFICATIONS =
+ unreleasedFlag(2302, "enable_usi_battery_notifications", teamfood = true)
@JvmField val ENABLE_STYLUS_EDUCATION = unreleasedFlag(2303, "enable_stylus_education")
// 2400 - performance tools and debugging info
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
index d085db9..da91572 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
@@ -27,16 +27,24 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.RingerModeTracker
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import javax.inject.Inject
@SysUISingleton
@@ -46,6 +54,9 @@
private val userFileManager: UserFileManager,
private val ringerModeTracker: RingerModeTracker,
private val audioManager: AudioManager,
+ @Application private val coroutineScope: CoroutineScope,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
) : KeyguardQuickAffordanceConfig {
private var previousNonSilentMode: Int = DEFAULT_LAST_NON_SILENT_VALUE
@@ -58,7 +69,7 @@
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
ringerModeTracker.ringerModeInternal.asFlow()
- .onStart { emit(getLastNonSilentRingerMode()) }
+ .onStart { getLastNonSilentRingerMode() }
.distinctUntilChanged()
.onEach { mode ->
// only remember last non-SILENT ringer mode
@@ -87,54 +98,60 @@
activationState,
)
}
+ .flowOn(backgroundDispatcher)
override fun onTriggered(
expandable: Expandable?
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
- val newRingerMode: Int
- val currentRingerMode =
- ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE
- if (currentRingerMode == AudioManager.RINGER_MODE_SILENT) {
- newRingerMode = previousNonSilentMode
- } else {
- previousNonSilentMode = currentRingerMode
- newRingerMode = AudioManager.RINGER_MODE_SILENT
- }
+ coroutineScope.launch(backgroundDispatcher) {
+ val newRingerMode: Int
+ val currentRingerMode = audioManager.ringerModeInternal
+ if (currentRingerMode == AudioManager.RINGER_MODE_SILENT) {
+ newRingerMode = previousNonSilentMode
+ } else {
+ previousNonSilentMode = currentRingerMode
+ newRingerMode = AudioManager.RINGER_MODE_SILENT
+ }
- if (currentRingerMode != newRingerMode) {
- audioManager.ringerModeInternal = newRingerMode
+ if (currentRingerMode != newRingerMode) {
+ audioManager.ringerModeInternal = newRingerMode
+ }
}
return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
}
override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState =
- if (audioManager.isVolumeFixed) {
- KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
- } else {
- KeyguardQuickAffordanceConfig.PickerScreenState.Default()
+ withContext(backgroundDispatcher) {
+ if (audioManager.isVolumeFixed) {
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+ } else {
+ KeyguardQuickAffordanceConfig.PickerScreenState.Default()
+ }
}
/**
* Gets the last non-silent ringer mode from shared-preferences if it exists. This is
* cached by [MuteQuickAffordanceCoreStartable] while this affordance is selected
*/
- private fun getLastNonSilentRingerMode(): Int =
- userFileManager.getSharedPreferences(
- MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME,
- Context.MODE_PRIVATE,
- userTracker.userId
- ).getInt(
- LAST_NON_SILENT_RINGER_MODE_KEY,
- ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE
- )
+ private suspend fun getLastNonSilentRingerMode(): Int =
+ withContext(backgroundDispatcher) {
+ userFileManager.getSharedPreferences(
+ MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME,
+ Context.MODE_PRIVATE,
+ userTracker.userId
+ ).getInt(
+ LAST_NON_SILENT_RINGER_MODE_KEY,
+ ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE
+ )
+ }
private fun <T> LiveData<T>.asFlow(): Flow<T?> =
- conflatedCallbackFlow {
- val observer = Observer { value: T -> trySend(value) }
- observeForever(observer)
- send(value)
- awaitClose { removeObserver(observer) }
- }
+ conflatedCallbackFlow {
+ val observer = Observer { value: T -> trySend(value) }
+ observeForever(observer)
+ send(value)
+ awaitClose { removeObserver(observer) }
+ }.flowOn(mainDispatcher)
companion object {
const val LAST_NON_SILENT_RINGER_MODE_KEY = "key_last_non_silent_ringer_mode"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
index 12a6310..cd0805e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
@@ -23,15 +23,18 @@
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.RingerModeTracker
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
import javax.inject.Inject
/**
@@ -45,6 +48,7 @@
private val userFileManager: UserFileManager,
private val keyguardQuickAffordanceRepository: KeyguardQuickAffordanceRepository,
@Application private val coroutineScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
) : CoreStartable {
private val observer = Observer(this::updateLastNonSilentRingerMode)
@@ -72,15 +76,17 @@
}
private fun updateLastNonSilentRingerMode(lastRingerMode: Int) {
- if (AudioManager.RINGER_MODE_SILENT != lastRingerMode) {
- userFileManager.getSharedPreferences(
- MuteQuickAffordanceConfig.MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME,
- Context.MODE_PRIVATE,
- userTracker.userId
- )
- .edit()
- .putInt(MuteQuickAffordanceConfig.LAST_NON_SILENT_RINGER_MODE_KEY, lastRingerMode)
- .apply()
+ coroutineScope.launch(backgroundDispatcher) {
+ if (AudioManager.RINGER_MODE_SILENT != lastRingerMode) {
+ userFileManager.getSharedPreferences(
+ MuteQuickAffordanceConfig.MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME,
+ Context.MODE_PRIVATE,
+ userTracker.userId
+ )
+ .edit()
+ .putInt(MuteQuickAffordanceConfig.LAST_NON_SILENT_RINGER_MODE_KEY, lastRingerMode)
+ .apply()
+ }
}
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricRepository.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index de15890..0af596a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -33,7 +33,6 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.user.data.repository.UserRepository
import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
@@ -48,12 +47,13 @@
import kotlinx.coroutines.flow.transformLatest
/**
- * Acts as source of truth for biometric features.
+ * Acts as source of truth for biometric authentication related settings like enrollments, device
+ * policy, etc.
*
* Abstracts-away data sources and their schemas so the rest of the app doesn't need to worry about
* upstream changes.
*/
-interface BiometricRepository {
+interface BiometricSettingsRepository {
/** Whether any fingerprints are enrolled for the current user. */
val isFingerprintEnrolled: StateFlow<Boolean>
@@ -68,9 +68,8 @@
val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean>
}
-@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
-class BiometricRepositoryImpl
+class BiometricSettingsRepositoryImpl
@Inject
constructor(
context: Context,
@@ -82,7 +81,7 @@
@Application scope: CoroutineScope,
@Background backgroundDispatcher: CoroutineDispatcher,
@Main looper: Looper,
-) : BiometricRepository {
+) : BiometricSettingsRepository {
/** UserId of the current selected user. */
private val selectedUserId: Flow<Int> =
@@ -90,7 +89,7 @@
override val isFingerprintEnrolled: StateFlow<Boolean> =
selectedUserId
- .flatMapLatest { userId ->
+ .flatMapLatest {
conflatedCallbackFlow {
val callback =
object : AuthController.Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 35ae9b4..7bc6c34 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -41,31 +41,96 @@
*
* Make sure to add newly added flows to the logger.
*/
+interface KeyguardBouncerRepository {
+ /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
+ val primaryBouncerVisible: StateFlow<Boolean>
+ val primaryBouncerShow: StateFlow<KeyguardBouncerModel?>
+ val primaryBouncerShowingSoon: StateFlow<Boolean>
+ val primaryBouncerHide: StateFlow<Boolean>
+ val primaryBouncerStartingToHide: StateFlow<Boolean>
+ val primaryBouncerStartingDisappearAnimation: StateFlow<Runnable?>
+ /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
+ val primaryBouncerScrimmed: StateFlow<Boolean>
+ /**
+ * Set how much of the notification panel is showing on the screen.
+ * ```
+ * 0f = panel fully hidden = bouncer fully showing
+ * 1f = panel fully showing = bouncer fully hidden
+ * ```
+ */
+ val panelExpansionAmount: StateFlow<Float>
+ val keyguardPosition: StateFlow<Float>
+ val onScreenTurnedOff: StateFlow<Boolean>
+ val isBackButtonEnabled: StateFlow<Boolean?>
+ /** Determines if user is already unlocked */
+ val keyguardAuthenticated: StateFlow<Boolean?>
+ val showMessage: StateFlow<BouncerShowMessageModel?>
+ val resourceUpdateRequests: StateFlow<Boolean>
+ val bouncerPromptReason: Int
+ val bouncerErrorMessage: CharSequence?
+ val isAlternateBouncerVisible: StateFlow<Boolean>
+ val isAlternateBouncerUIAvailable: StateFlow<Boolean>
+ var lastAlternateBouncerVisibleTime: Long
+
+ fun setPrimaryScrimmed(isScrimmed: Boolean)
+
+ fun setPrimaryVisible(isVisible: Boolean)
+
+ fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?)
+
+ fun setPrimaryShowingSoon(showingSoon: Boolean)
+
+ fun setPrimaryHide(hide: Boolean)
+
+ fun setPrimaryStartingToHide(startingToHide: Boolean)
+
+ fun setPrimaryStartDisappearAnimation(runnable: Runnable?)
+
+ fun setPanelExpansion(panelExpansion: Float)
+
+ fun setKeyguardPosition(keyguardPosition: Float)
+
+ fun setResourceUpdateRequests(willUpdateResources: Boolean)
+
+ fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?)
+
+ fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?)
+
+ fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean)
+
+ fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean)
+
+ fun setAlternateVisible(isVisible: Boolean)
+
+ fun setAlternateBouncerUIAvailable(isAvailable: Boolean)
+}
+
@SysUISingleton
-class KeyguardBouncerRepository
+class KeyguardBouncerRepositoryImpl
@Inject
constructor(
private val viewMediatorCallback: ViewMediatorCallback,
private val clock: SystemClock,
@Application private val applicationScope: CoroutineScope,
@BouncerLog private val buffer: TableLogBuffer,
-) {
+) : KeyguardBouncerRepository {
/** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
private val _primaryBouncerVisible = MutableStateFlow(false)
- val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
+ override val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
- val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
+ override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
private val _primaryBouncerShowingSoon = MutableStateFlow(false)
- val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
+ override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
private val _primaryBouncerHide = MutableStateFlow(false)
- val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
+ override val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
private val _primaryBouncerStartingToHide = MutableStateFlow(false)
- val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
+ override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
- val primaryBouncerStartingDisappearAnimation = _primaryBouncerDisappearAnimation.asStateFlow()
+ override val primaryBouncerStartingDisappearAnimation =
+ _primaryBouncerDisappearAnimation.asStateFlow()
/** Determines if we want to instantaneously show the primary bouncer instead of translating. */
private val _primaryBouncerScrimmed = MutableStateFlow(false)
- val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
+ override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
/**
* Set how much of the notification panel is showing on the screen.
* ```
@@ -74,46 +139,46 @@
* ```
*/
private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
- val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
+ override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
private val _keyguardPosition = MutableStateFlow(0f)
- val keyguardPosition = _keyguardPosition.asStateFlow()
+ override val keyguardPosition = _keyguardPosition.asStateFlow()
private val _onScreenTurnedOff = MutableStateFlow(false)
- val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
+ override val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
- val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
+ override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
/** Determines if user is already unlocked */
- val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
+ override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
- val showMessage = _showMessage.asStateFlow()
+ override val showMessage = _showMessage.asStateFlow()
private val _resourceUpdateRequests = MutableStateFlow(false)
- val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
- val bouncerPromptReason: Int
+ override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
+ override val bouncerPromptReason: Int
get() = viewMediatorCallback.bouncerPromptReason
- val bouncerErrorMessage: CharSequence?
+ override val bouncerErrorMessage: CharSequence?
get() = viewMediatorCallback.consumeCustomMessage()
/** Values associated with the AlternateBouncer */
private val _isAlternateBouncerVisible = MutableStateFlow(false)
- val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
- var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE
+ override val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
+ override var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE
private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false)
- val isAlternateBouncerUIAvailable: StateFlow<Boolean> =
+ override val isAlternateBouncerUIAvailable: StateFlow<Boolean> =
_isAlternateBouncerUIAvailable.asStateFlow()
init {
setUpLogging()
}
- fun setPrimaryScrimmed(isScrimmed: Boolean) {
+ override fun setPrimaryScrimmed(isScrimmed: Boolean) {
_primaryBouncerScrimmed.value = isScrimmed
}
- fun setPrimaryVisible(isVisible: Boolean) {
+ override fun setPrimaryVisible(isVisible: Boolean) {
_primaryBouncerVisible.value = isVisible
}
- fun setAlternateVisible(isVisible: Boolean) {
+ override fun setAlternateVisible(isVisible: Boolean) {
if (isVisible && !_isAlternateBouncerVisible.value) {
lastAlternateBouncerVisibleTime = clock.uptimeMillis()
} else if (!isVisible) {
@@ -122,55 +187,55 @@
_isAlternateBouncerVisible.value = isVisible
}
- fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
+ override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
_isAlternateBouncerUIAvailable.value = isAvailable
}
- fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
+ override fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
_primaryBouncerShow.value = keyguardBouncerModel
}
- fun setPrimaryShowingSoon(showingSoon: Boolean) {
+ override fun setPrimaryShowingSoon(showingSoon: Boolean) {
_primaryBouncerShowingSoon.value = showingSoon
}
- fun setPrimaryHide(hide: Boolean) {
+ override fun setPrimaryHide(hide: Boolean) {
_primaryBouncerHide.value = hide
}
- fun setPrimaryStartingToHide(startingToHide: Boolean) {
+ override fun setPrimaryStartingToHide(startingToHide: Boolean) {
_primaryBouncerStartingToHide.value = startingToHide
}
- fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
+ override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
_primaryBouncerDisappearAnimation.value = runnable
}
- fun setPanelExpansion(panelExpansion: Float) {
+ override fun setPanelExpansion(panelExpansion: Float) {
_panelExpansionAmount.value = panelExpansion
}
- fun setKeyguardPosition(keyguardPosition: Float) {
+ override fun setKeyguardPosition(keyguardPosition: Float) {
_keyguardPosition.value = keyguardPosition
}
- fun setResourceUpdateRequests(willUpdateResources: Boolean) {
+ override fun setResourceUpdateRequests(willUpdateResources: Boolean) {
_resourceUpdateRequests.value = willUpdateResources
}
- fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
+ override fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
_showMessage.value = bouncerShowMessageModel
}
- fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
+ override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
_keyguardAuthenticated.value = keyguardAuthenticated
}
- fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
+ override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
_isBackButtonEnabled.value = isBackButtonEnabled
}
- fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
+ override fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
_onScreenTurnedOff.value = onScreenTurnedOff
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index cc99eb7..4a262f5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -31,10 +31,16 @@
@Binds
fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository
- @Binds fun biometricRepository(impl: BiometricRepositoryImpl): BiometricRepository
+ @Binds
+ fun biometricSettingsRepository(
+ impl: BiometricSettingsRepositoryImpl
+ ): BiometricSettingsRepository
@Binds
fun deviceEntryFingerprintAuthRepository(
impl: DeviceEntryFingerprintAuthRepositoryImpl
): DeviceEntryFingerprintAuthRepository
+
+ @Binds
+ fun keyguardBouncerRepository(impl: KeyguardBouncerRepositoryImpl): KeyguardBouncerRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
index 6020ef8..6452e0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
@@ -20,7 +20,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.BiometricRepository
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer
@@ -34,7 +34,7 @@
@Inject
constructor(
private val bouncerRepository: KeyguardBouncerRepository,
- private val biometricRepository: BiometricRepository,
+ private val biometricSettingsRepository: BiometricSettingsRepository,
private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
private val systemClock: SystemClock,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@@ -99,9 +99,9 @@
fun canShowAlternateBouncerForFingerprint(): Boolean {
return if (isModernAlternateBouncerEnabled) {
bouncerRepository.isAlternateBouncerUIAvailable.value &&
- biometricRepository.isFingerprintEnrolled.value &&
- biometricRepository.isStrongBiometricAllowed.value &&
- biometricRepository.isFingerprintEnabledByDevicePolicy.value &&
+ biometricSettingsRepository.isFingerprintEnrolled.value &&
+ biometricSettingsRepository.isStrongBiometricAllowed.value &&
+ biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value &&
!deviceEntryFingerprintAuthRepository.isLockedOut.value
} else {
legacyAlternateBouncer != null &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index ce61f2f..86f65dde 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -21,6 +21,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake
@@ -44,6 +45,7 @@
override fun start() {
listenForDozingToLockscreen()
+ listenForDozingToGone()
}
private fun listenForDozingToLockscreen() {
@@ -68,6 +70,28 @@
}
}
+ private fun listenForDozingToGone() {
+ scope.launch {
+ keyguardInteractor.biometricUnlockState
+ .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { (biometricUnlockState, lastStartedTransition) ->
+ if (
+ lastStartedTransition.to == KeyguardState.DOZING &&
+ isWakeAndUnlock(biometricUnlockState)
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.DOZING,
+ KeyguardState.GONE,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
return ValueAnimator().apply {
setInterpolator(Interpolators.LINEAR)
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 57c3b31..fb0fb1f 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
@@ -154,7 +154,11 @@
val slots = repository.get().getSlotPickerRepresentations()
val slot = slots.find { it.id == slotId } ?: return false
val selections =
- repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).toMutableList()
+ repository
+ .get()
+ .getCurrentSelections()
+ .getOrDefault(slotId, emptyList())
+ .toMutableList()
val alreadySelected = selections.remove(affordanceId)
if (!alreadySelected) {
while (selections.size > 0 && selections.size >= slot.maxSelectedAffordances) {
@@ -203,7 +207,11 @@
}
val selections =
- repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).toMutableList()
+ repository
+ .get()
+ .getCurrentSelections()
+ .getOrDefault(slotId, emptyList())
+ .toMutableList()
return if (selections.remove(affordanceId)) {
repository
.get()
@@ -367,6 +375,10 @@
name = Contract.FlagsTable.FLAG_NAME_WALLPAPER_FULLSCREEN_PREVIEW,
value = featureFlags.isEnabled(Flags.WALLPAPER_FULLSCREEN_PREVIEW),
),
+ KeyguardPickerFlag(
+ name = Contract.FlagsTable.FLAG_NAME_MONOCHROMATIC_THEME,
+ value = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEME)
+ )
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index ad6dbea..53c80f6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -19,7 +19,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.BOUNCER
@@ -31,9 +30,6 @@
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
import javax.inject.Inject
-import kotlin.math.max
-import kotlin.math.min
-import kotlin.time.Duration
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
@@ -104,38 +100,4 @@
/* The last completed [KeyguardState] transition */
val finishedKeyguardState: Flow<KeyguardState> =
finishedKeyguardTransitionStep.map { step -> step.to }
-
- /**
- * Transitions will occur over a [totalDuration] with [TransitionStep]s being emitted in the
- * range of [0, 1]. View animations should begin and end within a subset of this range. This
- * function maps the [startTime] and [duration] into [0, 1], when this subset is valid.
- */
- fun transitionStepAnimation(
- flow: Flow<TransitionStep>,
- params: AnimationParams,
- totalDuration: Duration,
- ): Flow<Float> {
- val start = (params.startTime / totalDuration).toFloat()
- val chunks = (totalDuration / params.duration).toFloat()
- var isRunning = false
- return flow
- .map { step ->
- val value = (step.value - start) * chunks
- if (step.transitionState == STARTED) {
- // When starting, make sure to always emit. If a transition is started from the
- // middle, it is possible this animation is being skipped but we need to inform
- // the ViewModels of the last update
- isRunning = true
- max(0f, min(1f, value))
- } else if (isRunning && value >= 1f) {
- // Always send a final value of 1. Because of rounding, [value] may never be
- // exactly 1.
- isRunning = false
- 1f
- } else {
- value
- }
- }
- .filter { value -> value >= 0f && value <= 1f }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 6679b22..4579e37 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -111,6 +111,8 @@
0f
}
}
+ /** Allow for interaction when just about fully visible */
+ val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
// TODO(b/243685699): Move isScrimmed logic to data layer.
// TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
new file mode 100644
index 0000000..ca1e27c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.ui
+
+import android.view.animation.Interpolator
+import com.android.systemui.animation.Interpolators.LINEAR
+import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+/**
+ * For the given transition params, construct a flow using [createFlow] for the specified portion of
+ * the overall transition.
+ */
+class KeyguardTransitionAnimationFlow(
+ private val transitionDuration: Duration,
+ private val transitionFlow: Flow<TransitionStep>,
+) {
+ /**
+ * Transitions will occur over a [transitionDuration] with [TransitionStep]s being emitted in
+ * the range of [0, 1]. View animations should begin and end within a subset of this range. This
+ * function maps the [startTime] and [duration] into [0, 1], when this subset is valid.
+ */
+ fun createFlow(
+ duration: Duration,
+ onStep: (Float) -> Float,
+ startTime: Duration = 0.milliseconds,
+ onCancel: (() -> Float)? = null,
+ onFinish: (() -> Float)? = null,
+ interpolator: Interpolator = LINEAR,
+ ): Flow<Float> {
+ if (!duration.isPositive()) {
+ throw IllegalArgumentException("duration must be a positive number: $duration")
+ }
+ if ((startTime + duration).compareTo(transitionDuration) > 0) {
+ throw IllegalArgumentException(
+ "startTime($startTime) + duration($duration) must be" +
+ " <= transitionDuration($transitionDuration)"
+ )
+ }
+
+ val start = (startTime / transitionDuration).toFloat()
+ val chunks = (transitionDuration / duration).toFloat()
+ var isComplete = true
+
+ fun stepToValue(step: TransitionStep): Float? {
+ val value = (step.value - start) * chunks
+ return when (step.transitionState) {
+ // When starting, make sure to always emit. If a transition is started from the
+ // middle, it is possible this animation is being skipped but we need to inform
+ // the ViewModels of the last update
+ STARTED -> {
+ isComplete = false
+ max(0f, min(1f, value))
+ }
+ // Always send a final value of 1. Because of rounding, [value] may never be
+ // exactly 1.
+ RUNNING ->
+ if (isComplete) {
+ null
+ } else if (value >= 1f) {
+ isComplete = true
+ 1f
+ } else if (value >= 0f) {
+ value
+ } else {
+ null
+ }
+ else -> null
+ }?.let { onStep(interpolator.getInterpolation(it)) }
+ }
+
+ return transitionFlow
+ .map { step ->
+ when (step.transitionState) {
+ STARTED -> stepToValue(step)
+ RUNNING -> stepToValue(step)
+ CANCELED -> onCancel?.invoke()
+ FINISHED -> onFinish?.invoke()
+ }
+ }
+ .filterNotNull()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index c1731e0..9f09d53 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -154,6 +154,12 @@
}
launch {
+ viewModel.isInteractable.collect { isInteractable ->
+ hostViewController.setInteractable(isInteractable)
+ }
+ }
+
+ launch {
viewModel.isBouncerVisible
.filter { !it }
.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 8808574..adde595 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -177,7 +177,8 @@
val receiver =
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
- clockController.clock?.events?.onTimeTick()
+ clockController.clock?.smallClock?.events?.onTimeTick()
+ clockController.clock?.largeClock?.events?.onTimeTick()
}
}
broadcastDispatcher.registerReceiver(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index 6627865..8d6545a4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -21,15 +21,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
-import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
-import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
/**
* Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -41,39 +36,46 @@
constructor(
private val interactor: KeyguardTransitionInteractor,
) {
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_LOCKSCREEN_DURATION,
+ transitionFlow = interactor.dreamingToLockscreenTransition,
+ )
/** Dream overlay y-translation on exit */
fun dreamOverlayTranslationY(translatePx: Int): Flow<Float> {
- return flowForAnimation(DREAM_OVERLAY_TRANSLATION_Y).map { value ->
- EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx
- }
+ return transitionAnimation.createFlow(
+ duration = 600.milliseconds,
+ onStep = { it * translatePx },
+ interpolator = EMPHASIZED_ACCELERATE,
+ )
}
/** Dream overlay views alpha - fade out */
- val dreamOverlayAlpha: Flow<Float> = flowForAnimation(DREAM_OVERLAY_ALPHA).map { 1f - it }
+ val dreamOverlayAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ duration = 250.milliseconds,
+ onStep = { 1f - it },
+ )
/** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
- return merge(
- flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
- -translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx)
- },
- // On end, reset the translation to 0
- interactor.dreamingToLockscreenTransition
- .filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
- .map { 0f }
+ return transitionAnimation.createFlow(
+ duration = TO_LOCKSCREEN_DURATION,
+ onStep = { value -> -translatePx + value * translatePx },
+ // Reset on cancel or finish
+ onFinish = { 0f },
+ onCancel = { 0f },
+ interpolator = EMPHASIZED_DECELERATE,
)
}
/** Lockscreen views alpha */
- val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA)
-
- private fun flowForAnimation(params: AnimationParams): Flow<Float> {
- return interactor.transitionStepAnimation(
- interactor.dreamingToLockscreenTransition,
- params,
- totalDuration = TO_LOCKSCREEN_DURATION
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ startTime = 233.milliseconds,
+ duration = 250.milliseconds,
+ onStep = { it },
)
- }
companion object {
/* Length of time before ending the dream activity, in order to start unoccluding */
@@ -81,11 +83,5 @@
@JvmField
val LOCKSCREEN_ANIMATION_DURATION_MS =
(TO_LOCKSCREEN_DURATION - DREAM_ANIMATION_DURATION).inWholeMilliseconds
-
- val DREAM_OVERLAY_TRANSLATION_Y = AnimationParams(duration = 600.milliseconds)
- val DREAM_OVERLAY_ALPHA = AnimationParams(duration = 250.milliseconds)
- val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_LOCKSCREEN_DURATION)
- val LOCKSCREEN_ALPHA =
- AnimationParams(startTime = 233.milliseconds, duration = 250.milliseconds)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
index 5a47960..f16827d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
@@ -20,15 +20,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
-import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
-import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
/** Breaks down GONE->DREAMING transition into discrete steps for corresponding views to consume. */
@SysUISingleton
@@ -38,32 +33,28 @@
private val interactor: KeyguardTransitionInteractor,
) {
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_DREAMING_DURATION,
+ transitionFlow = interactor.goneToDreamingTransition,
+ )
+
/** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
- return merge(
- flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
- (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx)
- },
- // On end, reset the translation to 0
- interactor.goneToDreamingTransition
- .filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
- .map { 0f }
+ return transitionAnimation.createFlow(
+ duration = 500.milliseconds,
+ onStep = { it * translatePx },
+ // Reset on cancel or finish
+ onFinish = { 0f },
+ onCancel = { 0f },
+ interpolator = EMPHASIZED_ACCELERATE,
)
}
/** Lockscreen views alpha */
- val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it }
-
- private fun flowForAnimation(params: AnimationParams): Flow<Float> {
- return interactor.transitionStepAnimation(
- interactor.goneToDreamingTransition,
- params,
- totalDuration = TO_DREAMING_DURATION
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ duration = 250.milliseconds,
+ onStep = { 1f - it },
)
- }
-
- companion object {
- val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = 500.milliseconds)
- val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds)
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index 737c35d..b8b3a8e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -39,6 +39,9 @@
/** Observe on bouncer visibility. */
val isBouncerVisible: Flow<Boolean> = interactor.isVisible
+ /** Can the user interact with the view? */
+ val isInteractable: Flow<Boolean> = interactor.isInteractable
+
/** Observe whether bouncer is showing. */
val show: Flow<KeyguardBouncerModel> = interactor.show
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
index e05adbd..bc9dc4f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
@@ -20,15 +20,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
-import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
-import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
/**
* Breaks down LOCKSCREEN->DREAMING transition into discrete steps for corresponding views to
@@ -40,35 +35,32 @@
constructor(
private val interactor: KeyguardTransitionInteractor,
) {
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_DREAMING_DURATION,
+ transitionFlow = interactor.lockscreenToDreamingTransition,
+ )
/** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
- return merge(
- flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
- (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx)
- },
- // On end, reset the translation to 0
- interactor.lockscreenToDreamingTransition
- .filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
- .map { 0f }
+ return transitionAnimation.createFlow(
+ duration = 500.milliseconds,
+ onStep = { it * translatePx },
+ // Reset on cancel or finish
+ onFinish = { 0f },
+ onCancel = { 0f },
+ interpolator = EMPHASIZED_ACCELERATE,
)
}
/** Lockscreen views alpha */
- val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it }
-
- private fun flowForAnimation(params: AnimationParams): Flow<Float> {
- return interactor.transitionStepAnimation(
- interactor.lockscreenToDreamingTransition,
- params,
- totalDuration = TO_DREAMING_DURATION
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ duration = 250.milliseconds,
+ onStep = { 1f - it },
)
- }
companion object {
@JvmField val DREAMING_ANIMATION_DURATION_MS = TO_DREAMING_DURATION.inWholeMilliseconds
-
- val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = 500.milliseconds)
- val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
index 22d292e..a60665a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
@@ -20,14 +20,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
-import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
/**
* Breaks down LOCKSCREEN->OCCLUDED transition into discrete steps for corresponding views to
@@ -39,33 +35,28 @@
constructor(
private val interactor: KeyguardTransitionInteractor,
) {
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_OCCLUDED_DURATION,
+ transitionFlow = interactor.lockscreenToOccludedTransition,
+ )
+
+ /** Lockscreen views alpha */
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ duration = 250.milliseconds,
+ onStep = { 1f - it },
+ )
/** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
- return merge(
- flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
- (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx)
- },
- // On end, reset the translation to 0
- interactor.lockscreenToOccludedTransition
- .filter { step -> step.transitionState == TransitionState.FINISHED }
- .map { 0f }
+ return transitionAnimation.createFlow(
+ duration = TO_OCCLUDED_DURATION,
+ onStep = { value -> value * translatePx },
+ // Reset on cancel or finish
+ onFinish = { 0f },
+ onCancel = { 0f },
+ interpolator = EMPHASIZED_ACCELERATE,
)
}
-
- /** Lockscreen views alpha */
- val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it }
-
- private fun flowForAnimation(params: AnimationParams): Flow<Float> {
- return interactor.transitionStepAnimation(
- interactor.lockscreenToOccludedTransition,
- params,
- totalDuration = TO_OCCLUDED_DURATION
- )
- }
-
- companion object {
- val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_OCCLUDED_DURATION)
- val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds)
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
index e804562..5770f3e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
@@ -20,11 +20,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
/**
* Breaks down OCCLUDED->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -36,28 +35,26 @@
constructor(
private val interactor: KeyguardTransitionInteractor,
) {
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_LOCKSCREEN_DURATION,
+ transitionFlow = interactor.occludedToLockscreenTransition,
+ )
+
/** Lockscreen views y-translation */
fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
- return flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
- -translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx)
- }
- }
-
- /** Lockscreen views alpha */
- val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA)
-
- private fun flowForAnimation(params: AnimationParams): Flow<Float> {
- return interactor.transitionStepAnimation(
- interactor.occludedToLockscreenTransition,
- params,
- totalDuration = TO_LOCKSCREEN_DURATION
+ return transitionAnimation.createFlow(
+ duration = TO_LOCKSCREEN_DURATION,
+ onStep = { value -> -translatePx + value * translatePx },
+ interpolator = EMPHASIZED_DECELERATE,
)
}
- companion object {
- @JvmField val LOCKSCREEN_ANIMATION_DURATION_MS = TO_LOCKSCREEN_DURATION.inWholeMilliseconds
- val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_LOCKSCREEN_DURATION)
- val LOCKSCREEN_ALPHA =
- AnimationParams(startTime = 233.milliseconds, duration = 250.milliseconds)
- }
+ /** Lockscreen views alpha */
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ startTime = 233.milliseconds,
+ duration = 250.milliseconds,
+ onStep = { it },
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
new file mode 100644
index 0000000..5acaa46
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.log
+
+import android.graphics.Point
+import android.graphics.Rect
+import android.graphics.RectF
+import androidx.core.graphics.toRectF
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.ScreenDecorationsLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.ERROR
+import javax.inject.Inject
+
+private const val TAG = "ScreenDecorationsLog"
+
+/**
+ * Helper class for logging for [com.android.systemui.ScreenDecorations]
+ *
+ * To enable logcat echoing for an entire buffer:
+ *
+ * ```
+ * adb shell settings put global systemui/buffer/ScreenDecorationsLog <logLevel>
+ *
+ * ```
+ */
+@SysUISingleton
+class ScreenDecorationsLogger
+@Inject
+constructor(
+ @ScreenDecorationsLog private val logBuffer: LogBuffer,
+) {
+ fun cameraProtectionBoundsForScanningOverlay(bounds: Rect) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { str1 = bounds.toShortString() },
+ { "Face scanning overlay present camera protection bounds: $str1" }
+ )
+ }
+
+ fun hwcLayerCameraProtectionBounds(bounds: Rect) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { str1 = bounds.toShortString() },
+ { "Hwc layer present camera protection bounds: $str1" }
+ )
+ }
+
+ fun dcvCameraBounds(id: Int, bounds: Rect) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = bounds.toShortString()
+ int1 = id
+ },
+ { "DisplayCutoutView id=$int1 present, camera protection bounds: $str1" }
+ )
+ }
+
+ fun cutoutViewNotInitialized() {
+ logBuffer.log(TAG, ERROR, "CutoutView not initialized showCameraProtection")
+ }
+
+ fun boundingRect(boundingRectangle: RectF, context: String) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = context
+ str2 = boundingRectangle.toShortString()
+ },
+ { "Bounding rect $str1 : $str2" }
+ )
+ }
+
+ fun boundingRect(boundingRectangle: Rect, context: String) {
+ boundingRect(boundingRectangle.toRectF(), context)
+ }
+
+ fun onMeasureDimensions(
+ widthMeasureSpec: Int,
+ heightMeasureSpec: Int,
+ measuredWidth: Int,
+ measuredHeight: Int
+ ) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ long1 = widthMeasureSpec.toLong()
+ long2 = heightMeasureSpec.toLong()
+ int1 = measuredWidth
+ int2 = measuredHeight
+ },
+ {
+ "Face scanning animation: widthMeasureSpec: $long1 measuredWidth: $int1, " +
+ "heightMeasureSpec: $long2 measuredHeight: $int2"
+ }
+ )
+ }
+
+ fun faceSensorLocation(faceSensorLocation: Point?) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = faceSensorLocation?.y?.times(2) ?: 0
+ str1 = "$faceSensorLocation"
+ },
+ { "Reinflating view: Face sensor location: $str1, faceScanningHeight: $int1" }
+ )
+ }
+
+ fun onSensorLocationChanged() {
+ logBuffer.log(TAG, DEBUG, "AuthControllerCallback in ScreenDecorations triggered")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index f61d135..c02fd9c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -350,6 +350,16 @@
}
/**
+ * Provides a {@link LogBuffer} for use by {@link com.android.systemui.ScreenDecorations}.
+ */
+ @Provides
+ @SysUISingleton
+ @ScreenDecorationsLog
+ public static LogBuffer provideScreenDecorationsLog(LogBufferFactory factory) {
+ return factory.create("ScreenDecorationsLog", 200);
+ }
+
+ /**
* Provides a {@link LogBuffer} for bluetooth-related logs.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/ScreenDecorationsLog.kt
similarity index 60%
rename from packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt
rename to packages/SystemUI/src/com/android/systemui/log/dagger/ScreenDecorationsLog.kt
index 67733e9..de2a8b6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ScreenDecorationsLog.kt
@@ -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.
@@ -11,15 +11,15 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
-package com.android.systemui.keyguard.shared.model
-import kotlin.time.Duration
-import kotlin.time.Duration.Companion.milliseconds
+package com.android.systemui.log.dagger
-/** Animation parameters */
-data class AnimationParams(
- val startTime: Duration = 0.milliseconds,
- val duration: Duration,
-)
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for ScreenDecorations added by SysUI. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class ScreenDecorationsLog
diff --git a/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java b/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
index a88a4ca..4f5bbb7 100644
--- a/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
@@ -28,11 +28,10 @@
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.text.Html;
-import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
-import android.text.style.TypefaceSpan;
import android.text.style.URLSpan;
import android.util.Slog;
import android.view.ContextThemeWrapper;
@@ -64,7 +63,10 @@
private String mAlertTitle;
private String mAlertBody;
- private String mAlertLearnMore;
+
+ private boolean mAlertLearnMoreLink;
+ private SpannableString mAlertLearnMore;
+
private AlertDialog.Builder mAlertDialog;
private AlertDialog mAlert;
private View mAlertView;
@@ -90,8 +92,20 @@
return;
}
- mAlertBody = getResources().getString(R.string.log_access_confirmation_body);
- mAlertLearnMore = getResources().getString(R.string.log_access_confirmation_learn_more);
+ mAlertBody = getString(R.string.log_access_confirmation_body);
+ mAlertLearnMoreLink = this.getResources()
+ .getBoolean(R.bool.log_access_confirmation_learn_more_as_link);
+ if (mAlertLearnMoreLink) {
+ mAlertLearnMore = new SpannableString(
+ getString(R.string.log_access_confirmation_learn_more));
+ mAlertLearnMore.setSpan(new URLSpan(
+ getString(R.string.log_access_confirmation_learn_more_url)),
+ 0, mAlertLearnMore.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ } else {
+ mAlertLearnMore = new SpannableString(
+ getString(R.string.log_access_confirmation_learn_more_at,
+ getString(R.string.log_access_confirmation_learn_more_url)));
+ }
// create View
int themeId = R.style.LogAccessDialogTheme;
@@ -180,15 +194,6 @@
return titleString;
}
- private Spannable styleFont(String text) {
- Spannable s = (Spannable) Html.fromHtml(text);
- for (URLSpan span : s.getSpans(0, s.length(), URLSpan.class)) {
- TypefaceSpan typefaceSpan = new TypefaceSpan("google-sans");
- s.setSpan(typefaceSpan, s.getSpanStart(span), s.getSpanEnd(span), 0);
- }
- return s;
- }
-
/**
* Returns the dialog view.
* If we cannot retrieve the package name, it returns null and we decline the full device log
@@ -207,13 +212,12 @@
.setText(mAlertTitle);
if (!TextUtils.isEmpty(mAlertLearnMore)) {
- Spannable mSpannableLearnMore = styleFont(mAlertLearnMore);
-
((TextView) view.findViewById(R.id.log_access_dialog_body))
- .setText(TextUtils.concat(mAlertBody, "\n\n", mSpannableLearnMore));
-
- ((TextView) view.findViewById(R.id.log_access_dialog_body))
+ .setText(TextUtils.concat(mAlertBody, "\n\n", mAlertLearnMore));
+ if (mAlertLearnMoreLink) {
+ ((TextView) view.findViewById(R.id.log_access_dialog_body))
.setMovementMethod(LinkMovementMethod.getInstance());
+ }
} else {
((TextView) view.findViewById(R.id.log_access_dialog_body))
.setText(mAlertBody);
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
index be18cbe..b7a2522 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
@@ -92,6 +92,9 @@
/** Whether explicit indicator exists */
val isExplicit: Boolean = false,
+
+ /** Track progress (0 - 1) to display for players where [resumption] is true */
+ val resumeProgress: Double? = null,
) {
companion object {
/** Media is playing on the local device */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
index bba5f35..a057c9f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
@@ -238,6 +238,24 @@
}
/**
+ * Set the progress to a fixed percentage value that cannot be changed by the user.
+ *
+ * @param percent value between 0 and 1
+ */
+ fun updateStaticProgress(percent: Double) {
+ val position = (percent * 100).toInt()
+ _data =
+ Progress(
+ enabled = true,
+ seekAvailable = false,
+ playing = false,
+ scrubbing = false,
+ elapsedTime = position,
+ duration = 100,
+ )
+ }
+
+ /**
* Puts the seek bar into a resumption state.
*
* This should be called when the media session behind the controller has been destroyed.
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
index 8c1ec16..70f2dee 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
@@ -20,6 +20,7 @@
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
+import android.widget.SeekBar
import android.widget.TextView
import com.android.internal.widget.CachingIconView
import com.android.systemui.R
@@ -37,6 +38,7 @@
// Recommendation screen
lateinit var cardIcon: ImageView
lateinit var mediaAppIcons: List<CachingIconView>
+ lateinit var mediaProgressBars: List<SeekBar>
lateinit var cardTitle: TextView
val mediaCoverContainers =
@@ -82,6 +84,13 @@
if (updatedView) {
mediaAppIcons = mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) }
cardTitle = itemView.requireViewById(R.id.media_rec_title)
+ mediaProgressBars =
+ mediaCoverContainers.map {
+ it.requireViewById<SeekBar?>(R.id.media_progress_bar).apply {
+ // Media playback is in the direction of tape, not time, so it stays LTR
+ layoutDirection = View.LAYOUT_DIRECTION_LTR
+ }
+ }
} else {
cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt
index 1df42c6..dc7a4f1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt
@@ -41,10 +41,12 @@
val recommendations: List<SmartspaceAction>,
/** Intent for the user's initiated dismissal. */
val dismissIntent: Intent?,
- /** The timestamp in milliseconds that headphone is connected. */
+ /** The timestamp in milliseconds that the card was generated */
val headphoneConnectionTimeMillis: Long,
/** Instance ID for [MediaUiEventLogger] */
- val instanceId: InstanceId
+ val instanceId: InstanceId,
+ /** The timestamp in milliseconds indicating when the card should be removed */
+ val expiryTimeMs: Long,
) {
/**
* Indicates if all the data is valid.
@@ -86,5 +88,12 @@
}
}
+/** Key for extras [SmartspaceMediaData.cardAction] indicating why the card was sent */
+const val EXTRA_KEY_TRIGGER_SOURCE = "MEDIA_RECOMMENDATION_TRIGGER_SOURCE"
+/** Value for [EXTRA_KEY_TRIGGER_SOURCE] when the card is sent on headphone connection */
+const val EXTRA_VALUE_TRIGGER_HEADPHONE = "HEADPHONE_CONNECTION"
+/** Value for key [EXTRA_KEY_TRIGGER_SOURCE] when the card is sent as a regular update */
+const val EXTRA_VALUE_TRIGGER_PERIODIC = "PERIODIC_TRIGGER"
+
const val NUM_REQUIRED_RECOMMENDATIONS = 3
private val TAG = SmartspaceMediaData::class.simpleName!!
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index cf71d67..27f7b97 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -24,6 +24,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
+import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -66,7 +67,8 @@
private val lockscreenUserManager: NotificationLockscreenUserManager,
@Main private val executor: Executor,
private val systemClock: SystemClock,
- private val logger: MediaUiEventLogger
+ private val logger: MediaUiEventLogger,
+ private val mediaFlags: MediaFlags,
) : MediaDataManager.Listener {
private val _listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
internal val listeners: Set<MediaDataManager.Listener>
@@ -121,7 +123,9 @@
data: SmartspaceMediaData,
shouldPrioritize: Boolean
) {
- if (!data.isActive) {
+ // With persistent recommendation card, we could get a background update while inactive
+ // Otherwise, consider it an invalid update
+ if (!data.isActive && !mediaFlags.isPersistentSsCardEnabled()) {
Log.d(TAG, "Inactive recommendation data. Skip triggering.")
return
}
@@ -141,7 +145,7 @@
}
}
- val shouldReactivate = !hasActiveMedia() && hasAnyMedia()
+ val shouldReactivate = !hasActiveMedia() && hasAnyMedia() && data.isActive
if (timeSinceActive < smartspaceMaxAgeMillis) {
// It could happen there are existing active media resume cards, then we don't need to
@@ -169,7 +173,7 @@
)
}
}
- } else {
+ } else if (data.isActive) {
// Mark to prioritize Smartspace card if no recent media.
shouldPrioritizeMutable = true
}
@@ -252,7 +256,7 @@
if (dismissIntent == null) {
Log.w(
TAG,
- "Cannot create dismiss action click action: " + "extras missing dismiss_intent."
+ "Cannot create dismiss action click action: extras missing dismiss_intent."
)
} else if (
dismissIntent.getComponent() != null &&
@@ -264,15 +268,21 @@
} else {
broadcastSender.sendBroadcast(dismissIntent)
}
- smartspaceMediaData =
- EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- targetId = smartspaceMediaData.targetId,
- instanceId = smartspaceMediaData.instanceId
+
+ if (mediaFlags.isPersistentSsCardEnabled()) {
+ smartspaceMediaData = smartspaceMediaData.copy(isActive = false)
+ mediaDataManager.setRecommendationInactive(smartspaceMediaData.targetId)
+ } else {
+ smartspaceMediaData =
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+ targetId = smartspaceMediaData.targetId,
+ instanceId = smartspaceMediaData.instanceId,
+ )
+ mediaDataManager.dismissSmartspaceRecommendation(
+ smartspaceMediaData.targetId,
+ delay = 0L,
)
- mediaDataManager.dismissSmartspaceRecommendation(
- smartspaceMediaData.targetId,
- delay = 0L
- )
+ }
}
}
@@ -283,8 +293,15 @@
(smartspaceMediaData.isValid() || reactivatedKey != null))
/** Are there any media entries we should display? */
- fun hasAnyMediaOrRecommendation() =
- userEntries.isNotEmpty() || (smartspaceMediaData.isActive && smartspaceMediaData.isValid())
+ fun hasAnyMediaOrRecommendation(): Boolean {
+ val hasSmartspace =
+ if (mediaFlags.isPersistentSsCardEnabled()) {
+ smartspaceMediaData.isValid()
+ } else {
+ smartspaceMediaData.isActive && smartspaceMediaData.isValid()
+ }
+ return userEntries.isNotEmpty() || hasSmartspace
+ }
/** Are there any media notifications active (excluding the recommendation)? */
fun hasActiveMedia() = userEntries.any { it.value.active }
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 da2164e..0222ea3 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
@@ -50,7 +50,6 @@
import android.text.TextUtils
import android.util.Log
import androidx.media.utils.MediaConstants
-import com.android.internal.annotations.VisibleForTesting
import com.android.internal.logging.InstanceId
import com.android.systemui.Dumpable
import com.android.systemui.R
@@ -64,10 +63,13 @@
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.models.player.MediaDeviceData
import com.android.systemui.media.controls.models.player.MediaViewHolder
+import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_SOURCE
+import com.android.systemui.media.controls.models.recommendation.EXTRA_VALUE_TRIGGER_PERIODIC
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider
import com.android.systemui.media.controls.resume.MediaResumeListener
import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaDataUtils
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.ActivityStarter
@@ -119,7 +121,6 @@
appUid = Process.INVALID_UID
)
-@VisibleForTesting
internal val EMPTY_SMARTSPACE_MEDIA_DATA =
SmartspaceMediaData(
targetId = "INVALID",
@@ -129,7 +130,8 @@
recommendations = emptyList(),
dismissIntent = null,
headphoneConnectionTimeMillis = 0,
- instanceId = InstanceId.fakeInstanceId(-1)
+ instanceId = InstanceId.fakeInstanceId(-1),
+ expiryTimeMs = 0,
)
fun isMediaNotification(sbn: StatusBarNotification): Boolean {
@@ -548,6 +550,11 @@
if (DEBUG) Log.d(TAG, "Updating $key timedOut: $timedOut")
onMediaDataLoaded(key, key, it)
}
+
+ if (key == smartspaceMediaData.targetId) {
+ if (DEBUG) Log.d(TAG, "smartspace card expired")
+ dismissSmartspaceRecommendation(key, delay = 0L)
+ }
}
/** Called when the player's [PlaybackState] has been updated with new actions and/or state */
@@ -605,8 +612,8 @@
}
/**
- * Called whenever the recommendation has been expired, or swiped from QQS. This will make the
- * recommendation view to not be shown anymore during this headphone connection session.
+ * Called whenever the recommendation has been expired or removed by the user. This will remove
+ * the recommendation card entirely from the carousel.
*/
fun dismissSmartspaceRecommendation(key: String, delay: Long) {
if (smartspaceMediaData.targetId != key || !smartspaceMediaData.isValid()) {
@@ -628,6 +635,23 @@
)
}
+ /** Called when the recommendation card should no longer be visible in QQS or lockscreen */
+ fun setRecommendationInactive(key: String) {
+ if (!mediaFlags.isPersistentSsCardEnabled()) {
+ Log.e(TAG, "Only persistent recommendation can be inactive!")
+ return
+ }
+ if (DEBUG) Log.d(TAG, "Setting smartspace recommendation inactive")
+
+ if (smartspaceMediaData.targetId != key || !smartspaceMediaData.isValid()) {
+ // If this doesn't match, or we've already invalidated the data, no action needed
+ return
+ }
+
+ smartspaceMediaData = smartspaceMediaData.copy(isActive = false)
+ notifySmartspaceMediaDataLoaded(smartspaceMediaData.targetId, smartspaceMediaData)
+ }
+
private fun loadMediaDataInBgForResumption(
userId: Int,
desc: MediaDescription,
@@ -668,6 +692,11 @@
MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT &&
mediaFlags.isExplicitIndicatorEnabled()
+ val progress =
+ if (mediaFlags.isResumeProgressEnabled()) {
+ MediaDataUtils.getDescriptionProgress(desc.extras)
+ } else null
+
val mediaAction = getResumeMediaAction(resumeAction)
val lastActive = systemClock.elapsedRealtime()
foregroundExecutor.execute {
@@ -698,6 +727,7 @@
instanceId = instanceId,
appUid = appUid,
isExplicit = isExplicit,
+ resumeProgress = progress,
)
)
}
@@ -1261,12 +1291,25 @@
if (DEBUG) {
Log.d(TAG, "Set Smartspace media to be inactive for the data update")
}
- smartspaceMediaData =
- EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- targetId = smartspaceMediaData.targetId,
- instanceId = smartspaceMediaData.instanceId
+ if (mediaFlags.isPersistentSsCardEnabled()) {
+ // Smartspace uses this signal to hide the card (e.g. when it expires or user
+ // disconnects headphones), so treat as setting inactive when flag is on
+ smartspaceMediaData = smartspaceMediaData.copy(isActive = false)
+ notifySmartspaceMediaDataLoaded(
+ smartspaceMediaData.targetId,
+ smartspaceMediaData,
)
- notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = false)
+ } else {
+ smartspaceMediaData =
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+ targetId = smartspaceMediaData.targetId,
+ instanceId = smartspaceMediaData.instanceId,
+ )
+ notifySmartspaceMediaDataRemoved(
+ smartspaceMediaData.targetId,
+ immediately = false,
+ )
+ }
}
1 -> {
val newMediaTarget = mediaTargets.get(0)
@@ -1275,7 +1318,7 @@
return
}
if (DEBUG) Log.d(TAG, "Forwarding Smartspace media update.")
- smartspaceMediaData = toSmartspaceMediaData(newMediaTarget, isActive = true)
+ smartspaceMediaData = toSmartspaceMediaData(newMediaTarget)
notifySmartspaceMediaDataLoaded(smartspaceMediaData.targetId, smartspaceMediaData)
}
else -> {
@@ -1284,7 +1327,7 @@
Log.wtf(TAG, "More than 1 Smartspace Media Update. Resetting the status...")
notifySmartspaceMediaDataRemoved(
smartspaceMediaData.targetId,
- false /* immediately */
+ immediately = false,
)
smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
}
@@ -1490,21 +1533,28 @@
}
/**
- * Converts the pass-in SmartspaceTarget to SmartspaceMediaData with the pass-in active status.
+ * Converts the pass-in SmartspaceTarget to SmartspaceMediaData
*
* @return An empty SmartspaceMediaData with the valid target Id is returned if the
* SmartspaceTarget's data is invalid.
*/
- private fun toSmartspaceMediaData(
- target: SmartspaceTarget,
- isActive: Boolean
- ): SmartspaceMediaData {
+ private fun toSmartspaceMediaData(target: SmartspaceTarget): SmartspaceMediaData {
var dismissIntent: Intent? = null
if (target.baseAction != null && target.baseAction.extras != null) {
dismissIntent =
target.baseAction.extras.getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY)
as Intent?
}
+
+ val isActive =
+ when {
+ !mediaFlags.isPersistentSsCardEnabled() -> true
+ target.baseAction == null -> true
+ else ->
+ target.baseAction.extras.getString(EXTRA_KEY_TRIGGER_SOURCE) !=
+ EXTRA_VALUE_TRIGGER_PERIODIC
+ }
+
packageName(target)?.let {
return SmartspaceMediaData(
targetId = target.smartspaceTargetId,
@@ -1514,7 +1564,8 @@
recommendations = target.iconGrid,
dismissIntent = dismissIntent,
headphoneConnectionTimeMillis = target.creationTimeMillis,
- instanceId = logger.getNewInstanceId()
+ instanceId = logger.getNewInstanceId(),
+ expiryTimeMs = target.expiryTimeMillis,
)
}
return EMPTY_SMARTSPACE_MEDIA_DATA.copy(
@@ -1522,7 +1573,8 @@
isActive = isActive,
dismissIntent = dismissIntent,
headphoneConnectionTimeMillis = target.creationTimeMillis,
- instanceId = logger.getNewInstanceId()
+ instanceId = logger.getNewInstanceId(),
+ expiryTimeMs = target.expiryTimeMillis,
)
}
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 a898b00..aa46b14 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
@@ -23,7 +23,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -49,10 +51,12 @@
@Main private val mainExecutor: DelayableExecutor,
private val logger: MediaTimeoutLogger,
statusBarStateController: SysuiStatusBarStateController,
- private val systemClock: SystemClock
+ private val systemClock: SystemClock,
+ private val mediaFlags: MediaFlags,
) : MediaDataManager.Listener {
private val mediaListeners: MutableMap<String, PlaybackStateListener> = mutableMapOf()
+ private val recommendationListeners: MutableMap<String, RecommendationListener> = mutableMapOf()
/**
* Callback representing that a media object is now expired:
@@ -93,6 +97,16 @@
listener.doTimeout()
}
}
+
+ recommendationListeners.forEach { (key, listener) ->
+ if (
+ listener.cancellation != null &&
+ listener.expiration <= systemClock.currentTimeMillis()
+ ) {
+ logger.logTimeoutCancelled(key, "Timed out while dozing")
+ listener.doTimeout()
+ }
+ }
}
}
}
@@ -155,6 +169,30 @@
mediaListeners.remove(key)?.destroy()
}
+ override fun onSmartspaceMediaDataLoaded(
+ key: String,
+ data: SmartspaceMediaData,
+ shouldPrioritize: Boolean
+ ) {
+ if (!mediaFlags.isPersistentSsCardEnabled()) return
+
+ // First check if we already have a listener
+ recommendationListeners.get(key)?.let {
+ if (!it.destroyed) {
+ it.recommendationData = data
+ return
+ }
+ }
+
+ // Otherwise, create a new one
+ recommendationListeners[key] = RecommendationListener(key, data)
+ }
+
+ override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
+ if (!mediaFlags.isPersistentSsCardEnabled()) return
+ recommendationListeners.remove(key)?.destroy()
+ }
+
fun isTimedOut(key: String): Boolean {
return mediaListeners[key]?.timedOut ?: false
}
@@ -335,4 +373,53 @@
}
return true
}
+
+ /** Listens to changes in recommendation card data and schedules a timeout for its expiration */
+ private inner class RecommendationListener(var key: String, data: SmartspaceMediaData) {
+ private var timedOut = false
+ var destroyed = false
+ var expiration = Long.MAX_VALUE
+ private set
+ var cancellation: Runnable? = null
+ private set
+
+ var recommendationData: SmartspaceMediaData = data
+ set(value) {
+ destroyed = false
+ field = value
+ processUpdate()
+ }
+
+ init {
+ recommendationData = data
+ }
+
+ fun destroy() {
+ cancellation?.run()
+ cancellation = null
+ destroyed = true
+ }
+
+ private fun processUpdate() {
+ if (recommendationData.expiryTimeMs != expiration) {
+ // The expiry time changed - cancel and reschedule
+ val timeout =
+ recommendationData.expiryTimeMs -
+ recommendationData.headphoneConnectionTimeMillis
+ logger.logRecommendationTimeoutScheduled(key, timeout)
+ cancellation?.run()
+ cancellation = mainExecutor.executeDelayed({ doTimeout() }, timeout)
+ expiration = recommendationData.expiryTimeMs
+ }
+ }
+
+ fun doTimeout() {
+ cancellation?.run()
+ cancellation = null
+ logger.logTimeout(key)
+ timedOut = true
+ expiration = Long.MAX_VALUE
+ timeoutCallback(key, timedOut)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
index 8f3f054..f731dc0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
@@ -107,6 +107,17 @@
str1 = key
str2 = reason
},
- { "media timeout cancelled for $str1, reason: $str2" }
+ { "timeout cancelled for $str1, reason: $str2" }
+ )
+
+ fun logRecommendationTimeoutScheduled(key: String, timeout: Long) =
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = key
+ long1 = timeout
+ },
+ { "recommendation timeout scheduled for $str1 in $long1 ms" }
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index b2ad155..fac1d5e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -368,7 +368,7 @@
data: SmartspaceMediaData,
shouldPrioritize: Boolean
) {
- debugLogger.logRecommendationLoaded(key)
+ debugLogger.logRecommendationLoaded(key, data.isActive)
// Log the case where the hidden media carousel with the existed inactive resume
// media is shown by the Smartspace signal.
if (data.isActive) {
@@ -442,7 +442,12 @@
logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded)
}
} else {
- onSmartspaceMediaDataRemoved(data.targetId, immediately = true)
+ if (!mediaFlags.isPersistentSsCardEnabled()) {
+ // Handle update to inactive as a removal
+ onSmartspaceMediaDataRemoved(data.targetId, immediately = true)
+ } else {
+ addSmartspaceMediaRecommendations(key, data, shouldPrioritize)
+ }
}
}
@@ -633,7 +638,19 @@
) =
traceSection("MediaCarouselController#addSmartspaceMediaRecommendations") {
if (DEBUG) Log.d(TAG, "Updating smartspace target in carousel")
- if (MediaPlayerData.getMediaPlayer(key) != null) {
+ MediaPlayerData.getMediaPlayer(key)?.let {
+ if (mediaFlags.isPersistentSsCardEnabled()) {
+ // The card exists, but could have changed active state, so update for sorting
+ MediaPlayerData.addMediaRecommendation(
+ key,
+ data,
+ it,
+ shouldPrioritize,
+ systemClock,
+ debugLogger,
+ update = true,
+ )
+ }
Log.w(TAG, "Skip adding smartspace target in carousel")
return
}
@@ -672,7 +689,7 @@
newRecs,
shouldPrioritize,
systemClock,
- debugLogger
+ debugLogger,
)
updatePlayerToState(newRecs, noAnimation = true)
reorderAllPlayers(curVisibleMediaKey)
@@ -1225,17 +1242,18 @@
player: MediaControlPanel,
shouldPrioritize: Boolean,
clock: SystemClock,
- debugLogger: MediaCarouselControllerLogger? = null
+ debugLogger: MediaCarouselControllerLogger? = null,
+ update: Boolean = false
) {
shouldPrioritizeSs = shouldPrioritize
val removedPlayer = removeMediaPlayer(key)
- if (removedPlayer != null && removedPlayer != player) {
+ if (!update && removedPlayer != null && removedPlayer != player) {
debugLogger?.logPotentialMemoryLeak(key)
}
val sortKey =
MediaSortKey(
isSsMediaRec = true,
- EMPTY.copy(isPlaying = false),
+ EMPTY.copy(active = data.isActive, isPlaying = false),
key,
clock.currentTimeMillis(),
isSsReactivated = true
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
index eed1bd7..35bda15 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
@@ -48,8 +48,16 @@
fun logMediaRemoved(key: String) =
buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "removing player $str1" })
- fun logRecommendationLoaded(key: String) =
- buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "add recommendation $str1" })
+ fun logRecommendationLoaded(key: String, isActive: Boolean) =
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = key
+ bool1 = isActive
+ },
+ { "add recommendation $str1, active $bool1" }
+ )
fun logRecommendationRemoved(key: String, immediately: Boolean) =
buffer.log(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 2fd4f27..1d000eb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -57,6 +57,7 @@
import android.view.animation.Interpolator;
import android.widget.ImageButton;
import android.widget.ImageView;
+import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -116,8 +117,6 @@
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.time.SystemClock;
-import dagger.Lazy;
-
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
@@ -125,6 +124,7 @@
import javax.inject.Inject;
+import dagger.Lazy;
import kotlin.Triple;
import kotlin.Unit;
@@ -524,8 +524,13 @@
}
// Seek Bar
- final MediaController controller = getController();
- mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
+ if (data.getResumption() && data.getResumeProgress() != null) {
+ double progress = data.getResumeProgress();
+ mSeekBarViewModel.updateStaticProgress(progress);
+ } else {
+ final MediaController controller = getController();
+ mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
+ }
// Show the broadcast dialog button only when the le audio is enabled.
mShowBroadcastDialogButton =
@@ -732,9 +737,14 @@
contentDescription =
mRecommendationViewHolder.getGutsViewHolder().getGutsText().getText();
} else if (data != null) {
- contentDescription = mContext.getString(
- R.string.controls_media_smartspace_rec_description,
- data.getAppName(mContext));
+ if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
+ contentDescription = mContext.getString(
+ R.string.controls_media_smartspace_rec_header);
+ } else {
+ contentDescription = mContext.getString(
+ R.string.controls_media_smartspace_rec_description,
+ data.getAppName(mContext));
+ }
} else {
contentDescription = null;
}
@@ -1365,6 +1375,24 @@
hasSubtitle |= !TextUtils.isEmpty(subtitle);
TextView subtitleView = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex);
subtitleView.setText(subtitle);
+
+ // Set up progress bar
+ if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
+ SeekBar mediaProgressBar =
+ mRecommendationViewHolder.getMediaProgressBars().get(itemIndex);
+ TextView mediaSubtitle =
+ mRecommendationViewHolder.getMediaSubtitles().get(itemIndex);
+ // show progress bar if the recommended album is played.
+ Double progress = MediaDataUtils.getDescriptionProgress(recommendation.getExtras());
+ if (progress == null || progress <= 0.0) {
+ mediaProgressBar.setVisibility(View.GONE);
+ mediaSubtitle.setVisibility(View.VISIBLE);
+ } else {
+ mediaProgressBar.setProgress((int) (progress * 100));
+ mediaProgressBar.setVisibility(View.VISIBLE);
+ mediaSubtitle.setVisibility(View.GONE);
+ }
+ }
}
mSmartspaceMediaItemsCount = NUM_REQUIRED_RECOMMENDATIONS;
@@ -1439,6 +1467,12 @@
(title) -> title.setTextColor(textPrimaryColor));
mRecommendationViewHolder.getMediaSubtitles().forEach(
(subtitle) -> subtitle.setTextColor(textSecondaryColor));
+ if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
+ mRecommendationViewHolder.getMediaProgressBars().forEach(
+ (progressBar) -> progressBar.setProgressTintList(
+ ColorStateList.valueOf(textPrimaryColor))
+ );
+ }
mRecommendationViewHolder.getGutsViewHolder().setColors(colorScheme);
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
index bcfceaa..85282a1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
@@ -19,8 +19,12 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.os.Bundle;
import android.text.TextUtils;
+import androidx.core.math.MathUtils;
+import androidx.media.utils.MediaConstants;
+
/**
* Utility class with common methods for media controls
*/
@@ -50,4 +54,35 @@
: unknownName);
return applicationName;
}
+
+ /**
+ * Check the bundle for extras indicating the progress percentage
+ *
+ * @param extras
+ * @return the progress value between 0-1 inclusive if prsent, otherwise null
+ */
+ public static Double getDescriptionProgress(Bundle extras) {
+ if (!extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS)) {
+ return null;
+ }
+
+ int status = extras.getInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS);
+ switch (status) {
+ case MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED:
+ return 0.0;
+ case MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED:
+ return 1.0;
+ case MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED: {
+ if (extras
+ .containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE)) {
+ double percent = extras
+ .getDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE);
+ return MathUtils.clamp(percent, 0.0, 1.0);
+ } else {
+ return 0.5;
+ }
+ }
+ }
+ return null;
+ }
}
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 81efa36..c3fa76e 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
@@ -55,4 +55,10 @@
/** Check whether we show the updated recommendation card. */
fun isRecommendationCardUpdateEnabled() =
featureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)
+
+ /** Check whether to get progress information for resume players */
+ fun isResumeProgressEnabled() = featureFlags.isEnabled(Flags.MEDIA_RESUME_PROGRESS)
+
+ /** If true, do not automatically dismiss the recommendation card */
+ fun isPersistentSsCardEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_RECOMMENDATIONS)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index d6c6a81..9f5d372 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -16,14 +16,18 @@
package com.android.systemui.media.dialog;
-import static android.media.RouteListingPreference.Item.DISABLE_REASON_SUBSCRIPTION_REQUIRED;
+import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_GO_TO_APP;
+import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_NONE;
+import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_TRANSFER;
+import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED;
+import static android.media.RouteListingPreference.Item.SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED;
+import static android.media.RouteListingPreference.Item.SUBTEXT_SUBSCRIPTION_REQUIRED;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
-import android.media.RouteListingPreference;
import android.os.Build;
import android.util.Log;
import android.view.View;
@@ -194,16 +198,29 @@
updateFullItemClickListener(v -> onItemClick(v, device));
setSingleLineLayout(getItemTitle(device));
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
- && mController.isSubStatusSupported() && device.hasDisabledReason()) {
- //update to subtext with device status
+ && mController.isSubStatusSupported()
+ && mController.isAdvancedLayoutSupported() && device.hasSubtext()) {
+ boolean isActiveWithOngoingSession =
+ device.hasOngoingSession() && currentlyConnected;
+ if (isActiveWithOngoingSession) {
+ //Selected device which has ongoing session, disable seekbar since we
+ //only allow volume control on Host
+ mSeekBar.setVolume(0);
+ disableSeekBar();
+ mCurrentActivePosition = position;
+ }
setUpDeviceIcon(device);
- mSubTitleText.setText(
- Api34Impl.composeDisabledReason(device.getDisableReason(), mContext));
- updateConnectionFailedStatusIcon();
- updateFullItemClickListener(null);
- setTwoLineLayout(device, false /* bFocused */, false /* showSeekBar */,
+ mSubTitleText.setText(device.getSubtextString());
+ Drawable deviceStatusIcon =
+ Api34Impl.getDeviceStatusIconBasedOnSelectionBehavior(device, mContext);
+ if (deviceStatusIcon != null) {
+ updateDeviceStatusIcon(deviceStatusIcon);
+ }
+ updateClickActionBasedOnSelectionBehavior(device);
+ setTwoLineLayout(device, isActiveWithOngoingSession /* bFocused */,
+ isActiveWithOngoingSession /* showSeekBar */,
false /* showProgressBar */, true /* showSubtitle */,
- true /* showStatus */);
+ deviceStatusIcon != null /* showStatus */);
} else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) {
setUpDeviceIcon(device);
updateConnectionFailedStatusIcon();
@@ -220,6 +237,7 @@
false /* showEndTouchArea */);
} else if (mController.getSelectedMediaDevice().size() > 1
&& isDeviceIncluded(mController.getSelectedMediaDevice(), device)) {
+ // selected device in group
boolean isDeviceDeselectable = isDeviceIncluded(
mController.getDeselectableMediaDevice(), device);
if (!mController.isAdvancedLayoutSupported()) {
@@ -235,6 +253,7 @@
initSeekbar(device, isCurrentSeekbarInvisible);
} else if (!mController.hasAdjustVolumeUserRestriction()
&& currentlyConnected) {
+ // single selected device
if (isMutingExpectedDeviceExist
&& !mController.isCurrentConnectedDeviceRemote()) {
// mark as disconnected and set special click listener
@@ -266,6 +285,7 @@
initSeekbar(device, isCurrentSeekbarInvisible);
}
} else if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) {
+ //groupable device
setUpDeviceIcon(device);
updateGroupableCheckBox(false, true, device);
if (mController.isAdvancedLayoutSupported()) {
@@ -280,7 +300,12 @@
} else {
setUpDeviceIcon(device);
setSingleLineLayout(getItemTitle(device));
- updateFullItemClickListener(v -> onItemClick(v, device));
+ if (mController.isAdvancedLayoutSupported()
+ && mController.isSubStatusSupported()) {
+ updateClickActionBasedOnSelectionBehavior(device);
+ } else {
+ updateFullItemClickListener(v -> onItemClick(v, device));
+ }
}
}
}
@@ -292,12 +317,23 @@
ColorStateList(states, colors));
}
+ private void updateClickActionBasedOnSelectionBehavior(MediaDevice device) {
+ View.OnClickListener clickListener = Api34Impl.getClickListenerBasedOnSelectionBehavior(
+ device, mController, v -> onItemClick(v, device));
+ updateFullItemClickListener(clickListener);
+ }
+
private void updateConnectionFailedStatusIcon() {
mStatusIcon.setImageDrawable(
mContext.getDrawable(R.drawable.media_output_status_failed));
mStatusIcon.setColorFilter(mController.getColorItemContent());
}
+ private void updateDeviceStatusIcon(Drawable drawable) {
+ mStatusIcon.setImageDrawable(drawable);
+ mStatusIcon.setColorFilter(mController.getColorItemContent());
+ }
+
private void updateProgressBarColor() {
mProgressBar.getIndeterminateDrawable().setColorFilter(
new PorterDuffColorFilter(
@@ -411,13 +447,30 @@
@RequiresApi(34)
private static class Api34Impl {
@DoNotInline
- static String composeDisabledReason(@RouteListingPreference.Item.DisableReason int reason,
- Context context) {
- switch(reason) {
- case DISABLE_REASON_SUBSCRIPTION_REQUIRED:
- return context.getString(R.string.media_output_status_require_premium);
+ static View.OnClickListener getClickListenerBasedOnSelectionBehavior(MediaDevice device,
+ MediaOutputController controller, View.OnClickListener defaultTransferListener) {
+ switch (device.getSelectionBehavior()) {
+ case SELECTION_BEHAVIOR_NONE:
+ return null;
+ case SELECTION_BEHAVIOR_TRANSFER:
+ return defaultTransferListener;
+ case SELECTION_BEHAVIOR_GO_TO_APP:
+ return v -> controller.tryToLaunchInAppRoutingIntent(device.getId(), v);
}
- return "";
+ return null;
+ }
+
+ @DoNotInline
+ static Drawable getDeviceStatusIconBasedOnSelectionBehavior(MediaDevice device,
+ Context context) {
+ switch (device.getSubtext()) {
+ case SUBTEXT_AD_ROUTING_DISALLOWED:
+ case SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED:
+ return context.getDrawable(R.drawable.media_output_status_failed);
+ case SUBTEXT_SUBSCRIPTION_REQUIRED:
+ return context.getDrawable(R.drawable.media_output_status_help);
+ }
+ return null;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index dc75538..b5e829e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -278,12 +278,29 @@
mStatusIcon.setVisibility(showStatus ? View.VISIBLE : View.GONE);
mSeekBar.setAlpha(1);
mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE);
- final Drawable backgroundDrawable = mContext.getDrawable(
- R.drawable.media_output_item_background)
- .mutate();
- backgroundDrawable.setColorFilter(new PorterDuffColorFilter(
- mController.getColorItemBackground(),
- PorterDuff.Mode.SRC_IN));
+ final Drawable backgroundDrawable;
+ if (mController.isAdvancedLayoutSupported() && mController.isSubStatusSupported()) {
+ backgroundDrawable = mContext.getDrawable(
+ showSeekBar ? R.drawable.media_output_item_background_active
+ : R.drawable.media_output_item_background).mutate();
+ backgroundDrawable.setColorFilter(new PorterDuffColorFilter(
+ showSeekBar ? mController.getColorConnectedItemBackground()
+ : mController.getColorItemBackground(), PorterDuff.Mode.SRC_IN));
+ mIconAreaLayout.getBackground().setColorFilter(new PorterDuffColorFilter(
+ showSeekBar ? mController.getColorConnectedItemBackground()
+ : mController.getColorItemBackground(),
+ PorterDuff.Mode.SRC_IN));
+ ViewGroup.MarginLayoutParams params =
+ (ViewGroup.MarginLayoutParams) mItemLayout.getLayoutParams();
+ params.rightMargin = mController.getItemMarginEndDefault();
+ } else {
+ backgroundDrawable = mContext.getDrawable(
+ R.drawable.media_output_item_background)
+ .mutate();
+ backgroundDrawable.setColorFilter(new PorterDuffColorFilter(
+ mController.getColorItemBackground(),
+ PorterDuff.Mode.SRC_IN));
+ }
mItemLayout.setBackground(backgroundDrawable);
mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
mSubTitleText.setVisibility(showSubtitle ? View.VISIBLE : View.GONE);
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 4803371..35819e3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -263,10 +263,10 @@
mMediaOutputController.releaseSession();
dismiss();
});
- mAppButton.setOnClickListener(v -> mMediaOutputController.tryToLaunchMediaApplication());
+ mAppButton.setOnClickListener(mMediaOutputController::tryToLaunchMediaApplication);
if (mMediaOutputController.isAdvancedLayoutSupported()) {
mMediaMetadataSectionLayout.setOnClickListener(
- v -> mMediaOutputController.tryToLaunchMediaApplication());
+ mMediaOutputController::tryToLaunchMediaApplication);
}
}
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 5f5c686..d234dff 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -16,6 +16,8 @@
package com.android.systemui.media.dialog;
+import static android.media.RouteListingPreference.ACTION_TRANSFER_MEDIA;
+import static android.media.RouteListingPreference.EXTRA_ROUTE_ID;
import static android.provider.Settings.ACTION_BLUETOOTH_SETTINGS;
import android.annotation.CallbackExecutor;
@@ -24,6 +26,7 @@
import android.app.Notification;
import android.app.WallpaperColors;
import android.bluetooth.BluetoothLeBroadcast;
+import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -382,12 +385,29 @@
return mContext.getPackageManager().getLaunchIntentForPackage(mPackageName);
}
- void tryToLaunchMediaApplication() {
+ void tryToLaunchInAppRoutingIntent(String routeId, View view) {
+ ComponentName componentName = mLocalMediaManager.getLinkedItemComponentName();
+ if (componentName != null) {
+ ActivityLaunchAnimator.Controller controller =
+ mDialogLaunchAnimator.createActivityLaunchController(view);
+ Intent launchIntent = new Intent(ACTION_TRANSFER_MEDIA);
+ launchIntent.setComponent(componentName);
+ launchIntent.putExtra(EXTRA_ROUTE_ID, routeId);
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mCallback.dismissDialog();
+ mContext.startActivity(launchIntent);
+ mActivityStarter.startActivity(launchIntent, true, controller);
+ }
+ }
+
+ void tryToLaunchMediaApplication(View view) {
+ ActivityLaunchAnimator.Controller controller =
+ mDialogLaunchAnimator.createActivityLaunchController(view);
Intent launchIntent = getAppLaunchIntent();
if (launchIntent != null) {
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mCallback.dismissDialog();
- mContext.startActivity(launchIntent);
+ mActivityStarter.startActivity(launchIntent, true, controller);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index 1f27582..537dbb9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -136,9 +136,9 @@
),
) {
override fun isValidNextState(nextState: ChipStateSender): Boolean {
- return nextState == FAR_FROM_RECEIVER ||
- nextState == ALMOST_CLOSE_TO_START_CAST ||
- nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+ // Since _SUCCEEDED is the end of a transfer sequence, we should be able to move to any
+ // state that represents the beginning of a new sequence.
+ return stateIsStartOfSequence(nextState)
}
},
@@ -158,9 +158,9 @@
),
) {
override fun isValidNextState(nextState: ChipStateSender): Boolean {
- return nextState == FAR_FROM_RECEIVER ||
- nextState == ALMOST_CLOSE_TO_END_CAST ||
- nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+ // Since _SUCCEEDED is the end of a transfer sequence, we should be able to move to any
+ // state that represents the beginning of a new sequence.
+ return stateIsStartOfSequence(nextState)
}
},
@@ -173,9 +173,9 @@
endItem = SenderEndItem.Error,
) {
override fun isValidNextState(nextState: ChipStateSender): Boolean {
- return nextState == FAR_FROM_RECEIVER ||
- nextState == ALMOST_CLOSE_TO_START_CAST ||
- nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+ // Since _FAILED is the end of a transfer sequence, we should be able to move to any
+ // state that represents the beginning of a new sequence.
+ return stateIsStartOfSequence(nextState)
}
},
@@ -188,9 +188,9 @@
endItem = SenderEndItem.Error,
) {
override fun isValidNextState(nextState: ChipStateSender): Boolean {
- return nextState == FAR_FROM_RECEIVER ||
- nextState == ALMOST_CLOSE_TO_END_CAST ||
- nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+ // Since _FAILED is the end of a transfer sequence, we should be able to move to any
+ // state that represents the beginning of a new sequence.
+ return stateIsStartOfSequence(nextState)
}
},
@@ -210,9 +210,9 @@
}
override fun isValidNextState(nextState: ChipStateSender): Boolean {
- return nextState == FAR_FROM_RECEIVER ||
- nextState.transferStatus == TransferStatus.NOT_STARTED ||
- nextState.transferStatus == TransferStatus.IN_PROGRESS
+ // When far away, we can go to any state that represents the start of a transfer
+ // sequence.
+ return stateIsStartOfSequence(nextState)
}
};
@@ -227,6 +227,20 @@
return Text.Loaded(context.getString(stringResId!!, otherDeviceName))
}
+ /**
+ * Returns true if moving from this state to [nextState] is a valid transition.
+ *
+ * In general, we expect a media transfer go to through a sequence of states:
+ * For push-to-receiver:
+ * - ALMOST_CLOSE_TO_START_CAST => TRANSFER_TO_RECEIVER_TRIGGERED =>
+ * TRANSFER_TO_RECEIVER_(SUCCEEDED|FAILED)
+ * - ALMOST_CLOSE_TO_END_CAST => TRANSFER_TO_THIS_DEVICE_TRIGGERED =>
+ * TRANSFER_TO_THIS_DEVICE_(SUCCEEDED|FAILED)
+ *
+ * This method should validate that the states go through approximately that sequence.
+ *
+ * See b/221265848 for more details.
+ */
abstract fun isValidNextState(nextState: ChipStateSender): Boolean
companion object {
@@ -276,6 +290,18 @@
return currentState.isValidNextState(desiredState)
}
+
+ /**
+ * Returns true if [state] represents a state at the beginning of a sequence and false
+ * otherwise.
+ */
+ private fun stateIsStartOfSequence(state: ChipStateSender): Boolean {
+ return state == FAR_FROM_RECEIVER ||
+ state.transferStatus == TransferStatus.NOT_STARTED ||
+ // It's possible to skip the NOT_STARTED phase and go immediately into the
+ // IN_PROGRESS phase.
+ state.transferStatus == TransferStatus.IN_PROGRESS
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
index 1324d2c..c4a1ed4 100644
--- a/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
@@ -19,7 +19,6 @@
import android.view.WindowManagerGlobal
import com.android.app.motiontool.DdmHandleMotionTool
import com.android.app.motiontool.MotionToolManager
-import com.android.app.viewcapture.ViewCapture
import com.android.systemui.CoreStartable
import dagger.Binds
import dagger.Module
@@ -38,17 +37,12 @@
}
@Provides
- fun provideMotionToolManager(
- viewCapture: ViewCapture,
- windowManagerGlobal: WindowManagerGlobal
- ): MotionToolManager {
- return MotionToolManager.getInstance(viewCapture, windowManagerGlobal)
+ fun provideMotionToolManager(windowManagerGlobal: WindowManagerGlobal): MotionToolManager {
+ return MotionToolManager.getInstance(windowManagerGlobal)
}
@Provides
fun provideWindowManagerGlobal(): WindowManagerGlobal = WindowManagerGlobal.getInstance()
-
- @Provides fun provideViewCapture(): ViewCapture = ViewCapture.getInstance()
}
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 79b4b3a..35423f4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -191,7 +191,7 @@
private static final String EXTRA_TRANSIENT_STATE = "transient_state";
/** Allow some time inbetween the long press for back and recents. */
- private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200;
+ private static final int LOCK_TO_APP_GESTURE_TOLERANCE = 200;
private static final long AUTODIM_TIMEOUT_MS = 2250;
private final Context mContext;
@@ -1439,7 +1439,7 @@
// If we recently long-pressed the other button then they were
// long-pressed 'together'
- if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERENCE) {
+ if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERANCE) {
stopLockTaskMode = true;
return true;
} else if (v.getId() == btnId1) {
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 8d8903d..2522e1c 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -75,6 +75,8 @@
import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.volume.Events;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.text.NumberFormat;
@@ -83,8 +85,6 @@
import javax.inject.Inject;
-import dagger.Lazy;
-
/**
*/
@SysUISingleton
@@ -132,7 +132,6 @@
private static final String ACTION_AUTO_SAVER_NO_THANKS =
"PNW.autoSaverNoThanks";
- private static final String ACTION_ENABLE_SEVERE_BATTERY_DIALOG = "PNW.enableSevereDialog";
private static final String EXTRA_SCHEDULED_BY_PERCENTAGE =
"extra_scheduled_by_percentage";
public static final String BATTERY_SAVER_SCHEDULE_SCREEN_INTENT_ACTION =
@@ -155,7 +154,6 @@
private final Intent mOpenBatterySettings = settings(Intent.ACTION_POWER_USAGE_SUMMARY);
private final Intent mOpenBatterySaverSettings =
settings(Settings.ACTION_BATTERY_SAVER_SETTINGS);
- private final boolean mUseSevereDialog;
private int mBatteryLevel;
private int mBucket;
@@ -177,7 +175,6 @@
private ActivityStarter mActivityStarter;
private final BroadcastSender mBroadcastSender;
private final UiEventLogger mUiEventLogger;
- private GlobalSettings mGlobalSettings;
private final UserTracker mUserTracker;
private final Lazy<BatteryController> mBatteryControllerLazy;
private final DialogLaunchAnimator mDialogLaunchAnimator;
@@ -198,9 +195,7 @@
mBroadcastSender = broadcastSender;
mBatteryControllerLazy = batteryControllerLazy;
mDialogLaunchAnimator = dialogLaunchAnimator;
- mUseSevereDialog = mContext.getResources().getBoolean(R.bool.config_severe_battery_dialog);
mUiEventLogger = uiEventLogger;
- mGlobalSettings = globalSettings;
mUserTracker = userTracker;
}
@@ -287,20 +282,6 @@
}
protected void showWarningNotification() {
- if (mGlobalSettings.getInt(Global.LOW_POWER_MODE_REMINDER_ENABLED, 1) == 0) {
- return;
- }
- if (showSevereLowBatteryDialog()) {
- mBroadcastSender.sendBroadcast(new Intent(ACTION_ENABLE_SEVERE_BATTERY_DIALOG)
- .setPackage(mContext.getPackageName())
- .putExtra(EXTRA_SCHEDULED_BY_PERCENTAGE, isScheduledByPercentage())
- .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_FOREGROUND));
- // Reset the state once dialog been enabled
- dismissLowBatteryNotification();
- mPlaySound = false;
- return;
- }
if (isScheduledByPercentage()) {
return;
}
@@ -349,10 +330,6 @@
mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_POWER_LOW, n, UserHandle.ALL);
}
- private boolean showSevereLowBatteryDialog() {
- return mBucket < -1 && mUseSevereDialog;
- }
-
/**
* Checking battery saver schedule mode is set as "Based on percentage" or not.
*
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 464b6e7..048d40c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -48,6 +48,7 @@
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_FOOTER_DOT
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS
@@ -158,6 +159,7 @@
private const val DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT = false
private const val DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS = true
private const val DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS = true
+ private const val DEFAULT_TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP = true
}
override var newChangesSinceDialogWasDismissed = false
@@ -173,6 +175,9 @@
private var showUserVisibleJobs = DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS
+ private var informJobSchedulerOfPendingAppStop =
+ DEFAULT_TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP
+
override val includesUserVisibleJobs: Boolean
get() = showUserVisibleJobs
@@ -233,6 +238,11 @@
NAMESPACE_SYSTEMUI,
TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS)
+ informJobSchedulerOfPendingAppStop = deviceConfigProxy.getBoolean(
+ NAMESPACE_SYSTEMUI,
+ TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP,
+ DEFAULT_TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP)
+
try {
activityManager.registerForegroundServiceObserver(foregroundServiceObserver)
// Clumping FGS and user-visible jobs here and showing a single entry and button
@@ -262,10 +272,13 @@
showStopBtnForUserAllowlistedApps)
var wasShowingUserVisibleJobs = showUserVisibleJobs
showUserVisibleJobs = it.getBoolean(
- TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, showUserVisibleJobs)
+ TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, showUserVisibleJobs)
if (showUserVisibleJobs != wasShowingUserVisibleJobs) {
onShowUserVisibleJobsFlagChanged()
}
+ informJobSchedulerOfPendingAppStop = it.getBoolean(
+ TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+ informJobSchedulerOfPendingAppStop)
}
_isAvailable.value = deviceConfigProxy.getBoolean(
@@ -475,14 +488,11 @@
private fun stopPackage(userId: Int, packageName: String, timeStarted: Long) {
logEvent(stopped = true, packageName, userId, timeStarted)
val userPackageKey = UserPackage(userId, packageName)
- if (showUserVisibleJobs &&
- runningTaskIdentifiers[userPackageKey]?.hasRunningJobs() == true) {
+ if (showUserVisibleJobs || informJobSchedulerOfPendingAppStop) {
// TODO(255768978): allow fine-grained job control
- jobScheduler.stopUserVisibleJobsForUser(packageName, userId)
+ jobScheduler.notePendingUserRequestedAppStop(packageName, userId, "task manager")
}
- if (runningTaskIdentifiers[userPackageKey]?.hasFgs() == true) {
- activityManager.stopAppForUser(packageName, userId)
- }
+ activityManager.stopAppForUser(packageName, userId)
}
private fun onShowUserVisibleJobsFlagChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
index 5bc209a..7316f46 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -203,8 +203,7 @@
// For now, restrict to debug users.
return Build.isDebuggable()
&& mDreamSupported
- // TODO(b/257333623): Allow the Dock User to be non-SystemUser user in HSUM.
- && (!mDreamOnlyEnabledForDockUser || mUserTracker.getUserHandle().isSystem());
+ && (!mDreamOnlyEnabledForDockUser || mUserTracker.getUserInfo().isMain());
}
@VisibleForTesting
@@ -224,7 +223,8 @@
private ComponentName getActiveDream() {
try {
- final ComponentName[] dreams = mDreamManager.getDreamComponents();
+ final ComponentName[] dreams = mDreamManager.getDreamComponentsForUser(
+ mUserTracker.getUserId());
return dreams != null && dreams.length > 0 ? dreams[0] : null;
} catch (RemoteException e) {
Log.w(TAG, "Failed to get active dream", e);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 28dd986..d423048 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -259,17 +259,19 @@
Log.d(TAG, "setWifiIndicators: " + indicators);
}
mWifiInfo.mEnabled = indicators.enabled;
- if (indicators.qsIcon == null) {
- return;
- }
- mWifiInfo.mConnected = indicators.qsIcon.visible;
- mWifiInfo.mWifiSignalIconId = indicators.qsIcon.icon;
- mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription;
- mWifiInfo.mEnabled = indicators.enabled;
mWifiInfo.mSsid = indicators.description;
mWifiInfo.mIsTransient = indicators.isTransient;
mWifiInfo.mStatusLabel = indicators.statusLabel;
- refreshState(mWifiInfo);
+ if (indicators.qsIcon != null) {
+ mWifiInfo.mConnected = indicators.qsIcon.visible;
+ mWifiInfo.mWifiSignalIconId = indicators.qsIcon.icon;
+ mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription;
+ refreshState(mWifiInfo);
+ } else {
+ mWifiInfo.mConnected = false;
+ mWifiInfo.mWifiSignalIconId = 0;
+ mWifiInfo.mWifiSignalContentDescription = null;
+ }
}
@Override
@@ -533,6 +535,9 @@
if (DEBUG) {
Log.d(TAG, "handleUpdateEthernetState: " + "EthernetCallbackInfo = " + cb.toString());
}
+ if (!cb.mConnected) {
+ return;
+ }
final Resources r = mContext.getResources();
state.label = r.getString(R.string.quick_settings_internet_label);
state.state = Tile.STATE_ACTIVE;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt
index 70ea2b5..48aa60f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt
@@ -17,8 +17,10 @@
package com.android.systemui.screenshot
import android.content.pm.PackageManager
+import android.view.Display
import android.view.IWindowManager
import android.view.ViewGroup
+import android.view.WindowManager
import android.widget.TextView
import com.android.systemui.R
import javax.inject.Inject
@@ -34,8 +36,18 @@
* notified.
*/
fun maybeNotifyOfScreenshot(data: ScreenshotData): List<CharSequence> {
- // TODO: actually ask the window manager once API is available.
- return listOf()
+ // No notification for screenshots from overview.
+ if (data.source == WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW) return listOf()
+
+ // Notify listeners, retrieve a list of listening components.
+ val components = windowManager.notifyScreenshotListeners(Display.DEFAULT_DISPLAY)
+
+ // Convert component names to app names.
+ return components.map {
+ packageManager
+ .getActivityInfo(it, PackageManager.ComponentInfoFlags.of(0))
+ .loadLabel(packageManager)
+ }
}
fun populateView(view: ViewGroup, appNames: List<CharSequence>) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
index c891686..7a62bae 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -46,6 +46,8 @@
SCREENSHOT_SAVED(306),
@UiEvent(doc = "screenshot failed to save")
SCREENSHOT_NOT_SAVED(336),
+ @UiEvent(doc = "failed to capture screenshot")
+ SCREENSHOT_CAPTURE_FAILED(1281),
@UiEvent(doc = "screenshot preview tapped")
SCREENSHOT_PREVIEW_TAPPED(307),
@UiEvent(doc = "screenshot edit button tapped")
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 4214c8f..8035d19 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -26,6 +26,7 @@
import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
import static com.android.systemui.screenshot.LogConfig.DEBUG_SERVICE;
import static com.android.systemui.screenshot.LogConfig.logTag;
+import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED;
import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER;
import android.annotation.MainThread;
@@ -202,6 +203,7 @@
// animation and error notification.
if (!mUserManager.isUserUnlocked()) {
Log.w(TAG, "Skipping screenshot because storage is locked!");
+ logFailedRequest(request);
mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_save_user_locked_text);
callback.reportError();
@@ -212,6 +214,7 @@
mBgExecutor.execute(() -> {
Log.w(TAG, "Skipping screenshot because an IT admin has disabled "
+ "screenshots on the device");
+ logFailedRequest(request);
String blockedByAdminText = mDevicePolicyManager.getResources().getString(
SCREENSHOT_BLOCKED_BY_ADMIN,
() -> mContext.getString(R.string.screenshot_blocked_by_admin));
@@ -225,38 +228,43 @@
if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_METADATA)) {
Log.d(TAG, "Processing screenshot data");
ScreenshotData screenshotData = ScreenshotData.fromRequest(request);
- mProcessor.processAsync(screenshotData,
- (data) -> dispatchToController(data, onSaved, callback));
+ try {
+ mProcessor.processAsync(screenshotData,
+ (data) -> dispatchToController(data, onSaved, callback));
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to process screenshot request!", e);
+ logFailedRequest(request);
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ callback.reportError();
+ }
} else {
- mProcessor.processAsync(request,
- (r) -> dispatchToController(r, onSaved, callback));
+ try {
+ mProcessor.processAsync(request,
+ (r) -> dispatchToController(r, onSaved, callback));
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to process screenshot request!", e);
+ logFailedRequest(request);
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ callback.reportError();
+ }
}
}
private void dispatchToController(ScreenshotData screenshot,
Consumer<Uri> uriConsumer, RequestCallback callback) {
-
mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshot.getSource()), 0,
screenshot.getPackageNameString());
-
- if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
- && screenshot.getBitmap() == null) {
- Log.e(TAG, "Got null bitmap from screenshot message");
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- callback.reportError();
- return;
- }
-
mScreenshot.handleScreenshot(screenshot, uriConsumer, callback);
}
private void dispatchToController(ScreenshotRequest request,
Consumer<Uri> uriConsumer, RequestCallback callback) {
-
ComponentName topComponent = request.getTopComponent();
- mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(request.getSource()), 0,
- topComponent == null ? "" : topComponent.getPackageName());
+ String packageName = topComponent == null ? "" : topComponent.getPackageName();
+ mUiEventLogger.log(
+ ScreenshotEvent.getScreenshotSource(request.getSource()), 0, packageName);
switch (request.getType()) {
case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
@@ -275,21 +283,22 @@
int taskId = request.getTaskId();
int userId = request.getUserId();
- if (screenshot == null) {
- Log.e(TAG, "Got null bitmap from screenshot message");
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- callback.reportError();
- } else {
- mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
- taskId, userId, topComponent, uriConsumer, callback);
- }
+ mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
+ taskId, userId, topComponent, uriConsumer, callback);
break;
default:
- Log.w(TAG, "Invalid screenshot option: " + request.getType());
+ Log.wtf(TAG, "Invalid screenshot option: " + request.getType());
}
}
+ private void logFailedRequest(ScreenshotRequest request) {
+ ComponentName topComponent = request.getTopComponent();
+ String packageName = topComponent == null ? "" : topComponent.getPackageName();
+ mUiEventLogger.log(
+ ScreenshotEvent.getScreenshotSource(request.getSource()), 0, packageName);
+ mUiEventLogger.log(SCREENSHOT_CAPTURE_FAILED, 0, packageName);
+ }
+
private static void sendComplete(Messenger target) {
try {
if (DEBUG_CALLBACK) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
index 66b7842..1b728b8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
@@ -67,10 +67,7 @@
}
// If label wasn't loaded, use a default
- val badgedLabel =
- packageManager.getUserBadgedLabel(label ?: defaultFileAppName(), userHandle)
-
- return WorkProfileFirstRunData(badgedLabel, badgedIcon)
+ return WorkProfileFirstRunData(label ?: defaultFileAppName(), badgedIcon)
}
return null
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
new file mode 100644
index 0000000..f8d86a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
@@ -0,0 +1,289 @@
+/*
+ * 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.screenshot;
+
+import static com.android.systemui.screenshot.AppClipsTrampolineActivity.ACTION_FINISH_FROM_TRAMPOLINE;
+import static com.android.systemui.screenshot.AppClipsTrampolineActivity.EXTRA_RESULT_RECEIVER;
+import static com.android.systemui.screenshot.AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI;
+import static com.android.systemui.screenshot.AppClipsTrampolineActivity.PERMISSION_SELF;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+
+import androidx.activity.ComponentActivity;
+import androidx.lifecycle.ViewModelProvider;
+
+import com.android.settingslib.Utils;
+import com.android.systemui.R;
+
+import javax.inject.Inject;
+
+/**
+ * An {@link Activity} to take a screenshot for the App Clips flow and presenting a screenshot
+ * editing tool.
+ *
+ * <p>An App Clips flow includes:
+ * <ul>
+ * <li>Checking if calling activity meets the prerequisites. This is done by
+ * {@link AppClipsTrampolineActivity}.
+ * <li>Performing the screenshot.
+ * <li>Showing a screenshot editing tool.
+ * <li>Returning the screenshot to the {@link AppClipsTrampolineActivity} so that it can return
+ * the screenshot to the calling activity after explicit user consent.
+ * </ul>
+ *
+ * <p>This {@link Activity} runs in its own separate process to isolate memory intensive image
+ * editing from SysUI process.
+ *
+ * TODO(b/267309532): Polish UI and animations.
+ */
+public final class AppClipsActivity extends ComponentActivity {
+
+ private final AppClipsViewModel.Factory mViewModelFactory;
+ private final BroadcastReceiver mBroadcastReceiver;
+ private final IntentFilter mIntentFilter;
+
+ private View mLayout;
+ private View mRoot;
+ private ImageView mPreview;
+ private CropView mCropView;
+ private MagnifierView mMagnifierView;
+ private Button mSave;
+ private Button mCancel;
+ private AppClipsViewModel mViewModel;
+
+ private ResultReceiver mResultReceiver;
+
+ @Inject
+ public AppClipsActivity(AppClipsViewModel.Factory viewModelFactory) {
+ mViewModelFactory = viewModelFactory;
+
+ mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Trampoline activity was dismissed so finish this activity.
+ if (ACTION_FINISH_FROM_TRAMPOLINE.equals(intent.getAction())) {
+ if (!isFinishing()) {
+ // Nullify the ResultReceiver so that result cannot be sent as trampoline
+ // activity is already finishing.
+ mResultReceiver = null;
+ finish();
+ }
+ }
+ }
+ };
+
+ mIntentFilter = new IntentFilter(ACTION_FINISH_FROM_TRAMPOLINE);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ overridePendingTransition(0, 0);
+ super.onCreate(savedInstanceState);
+
+ // Register the broadcast receiver that informs when the trampoline activity is dismissed.
+ registerReceiver(mBroadcastReceiver, mIntentFilter, PERMISSION_SELF, null,
+ RECEIVER_NOT_EXPORTED);
+
+ Intent intent = getIntent();
+ mResultReceiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER, ResultReceiver.class);
+ if (mResultReceiver == null) {
+ setErrorThenFinish(Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ return;
+ }
+
+ // Inflate layout but don't add it yet as it should be added after the screenshot is ready
+ // for preview.
+ mLayout = getLayoutInflater().inflate(R.layout.app_clips_screenshot, null);
+ mRoot = mLayout.findViewById(R.id.root);
+
+ mSave = mLayout.findViewById(R.id.save);
+ mCancel = mLayout.findViewById(R.id.cancel);
+ mSave.setOnClickListener(this::onClick);
+ mCancel.setOnClickListener(this::onClick);
+
+ mMagnifierView = mLayout.findViewById(R.id.magnifier);
+ mCropView = mLayout.findViewById(R.id.crop_view);
+ mCropView.setCropInteractionListener(mMagnifierView);
+
+ mPreview = mLayout.findViewById(R.id.preview);
+ mPreview.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
+ updateImageDimensions());
+
+ mViewModel = new ViewModelProvider(this, mViewModelFactory).get(AppClipsViewModel.class);
+ mViewModel.getScreenshot().observe(this, this::setScreenshot);
+ mViewModel.getResultLiveData().observe(this, this::setResultThenFinish);
+ mViewModel.getErrorLiveData().observe(this, this::setErrorThenFinish);
+
+ if (savedInstanceState == null) {
+ mViewModel.performScreenshot();
+ }
+ }
+
+ @Override
+ public void finish() {
+ super.finish();
+ overridePendingTransition(0, 0);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ unregisterReceiver(mBroadcastReceiver);
+
+ // If neither error nor result was set, it implies that the activity is finishing due to
+ // some other reason such as user dismissing this activity using back gesture. Inform error.
+ if (isFinishing() && mViewModel.getErrorLiveData().getValue() == null
+ && mViewModel.getResultLiveData().getValue() == null) {
+ // Set error but don't finish as the activity is already finishing.
+ setError(Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ }
+ }
+
+ private void setScreenshot(Bitmap screenshot) {
+ // Set background, status and navigation bar colors as the activity is no longer
+ // translucent.
+ int colorBackgroundFloating = Utils.getColorAttr(this,
+ android.R.attr.colorBackgroundFloating).getDefaultColor();
+ mRoot.setBackgroundColor(colorBackgroundFloating);
+
+ BitmapDrawable drawable = new BitmapDrawable(getResources(), screenshot);
+ mPreview.setImageDrawable(drawable);
+ mPreview.setAlpha(1f);
+
+ mMagnifierView.setDrawable(drawable, screenshot.getWidth(), screenshot.getHeight());
+
+ // Screenshot is now available so set content view.
+ setContentView(mLayout);
+ }
+
+ private void onClick(View view) {
+ mSave.setEnabled(false);
+ mCancel.setEnabled(false);
+
+ int id = view.getId();
+ if (id == R.id.save) {
+ saveScreenshotThenFinish();
+ } else {
+ setErrorThenFinish(Intent.CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED);
+ }
+ }
+
+ private void saveScreenshotThenFinish() {
+ Drawable drawable = mPreview.getDrawable();
+ if (drawable == null) {
+ setErrorThenFinish(Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ return;
+ }
+
+ Rect bounds = mCropView.getCropBoundaries(drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight());
+
+ if (bounds.isEmpty()) {
+ setErrorThenFinish(Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ return;
+ }
+
+ updateImageDimensions();
+ mViewModel.saveScreenshotThenFinish(drawable, bounds);
+ }
+
+ private void setResultThenFinish(Uri uri) {
+ if (mResultReceiver == null) {
+ return;
+ }
+
+ Bundle data = new Bundle();
+ data.putInt(Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE,
+ Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS);
+ data.putParcelable(EXTRA_SCREENSHOT_URI, uri);
+ try {
+ mResultReceiver.send(Activity.RESULT_OK, data);
+ } catch (Exception e) {
+ // Do nothing.
+ }
+
+ // Nullify the ResultReceiver before finishing to avoid resending the result.
+ mResultReceiver = null;
+ finish();
+ }
+
+ private void setErrorThenFinish(int errorCode) {
+ setError(errorCode);
+ finish();
+ }
+
+ private void setError(int errorCode) {
+ if (mResultReceiver == null) {
+ return;
+ }
+
+ Bundle data = new Bundle();
+ data.putInt(Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, errorCode);
+ try {
+ mResultReceiver.send(RESULT_OK, data);
+ } catch (Exception e) {
+ // Do nothing.
+ }
+
+ // Nullify the ResultReceiver to avoid resending the result.
+ mResultReceiver = null;
+ }
+
+ private void updateImageDimensions() {
+ Drawable drawable = mPreview.getDrawable();
+ if (drawable == null) {
+ return;
+ }
+
+ Rect bounds = drawable.getBounds();
+ float imageRatio = bounds.width() / (float) bounds.height();
+ int previewWidth = mPreview.getWidth() - mPreview.getPaddingLeft()
+ - mPreview.getPaddingRight();
+ int previewHeight = mPreview.getHeight() - mPreview.getPaddingTop()
+ - mPreview.getPaddingBottom();
+ float viewRatio = previewWidth / (float) previewHeight;
+
+ if (imageRatio > viewRatio) {
+ // Image is full width and height is constrained, compute extra padding to inform
+ // CropView.
+ int imageHeight = (int) (previewHeight * viewRatio / imageRatio);
+ int extraPadding = (previewHeight - imageHeight) / 2;
+ mCropView.setExtraPadding(extraPadding, extraPadding);
+ mCropView.setImageWidth(previewWidth);
+ } else {
+ // Image is full height.
+ mCropView.setExtraPadding(mPreview.getPaddingTop(), mPreview.getPaddingBottom());
+ mCropView.setImageWidth((int) (previewHeight * imageRatio));
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
new file mode 100644
index 0000000..65fb4c9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
@@ -0,0 +1,65 @@
+/*
+ * 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.screenshot.appclips;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.view.Display;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Application;
+
+import javax.inject.Inject;
+
+/** An intermediary singleton object to help communicating with the cross process service. */
+@SysUISingleton
+public class AppClipsCrossProcessHelper {
+
+ private final ServiceConnector<IAppClipsScreenshotHelperService> mProxyConnector;
+
+ @Inject
+ public AppClipsCrossProcessHelper(@Application Context context) {
+ mProxyConnector = new ServiceConnector.Impl<IAppClipsScreenshotHelperService>(context,
+ new Intent(context, AppClipsScreenshotHelperService.class),
+ Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
+ | Context.BIND_NOT_VISIBLE, context.getUserId(),
+ IAppClipsScreenshotHelperService.Stub::asInterface);
+ }
+
+ /**
+ * Returns a {@link Bitmap} captured in the SysUI process, {@code null} in case of an error.
+ *
+ * <p>Note: The SysUI process captures a {@link ScreenshotHardwareBufferInternal} which is ok to
+ * pass around but not a {@link Bitmap}.
+ */
+ @Nullable
+ public Bitmap takeScreenshot() {
+ try {
+ AndroidFuture<ScreenshotHardwareBufferInternal> future =
+ mProxyConnector.postForResult(
+ service -> service.takeScreenshot(Display.DEFAULT_DISPLAY));
+ return future.get().createBitmapThenCloseBuffer();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java
new file mode 100644
index 0000000..6f8c365
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java
@@ -0,0 +1,69 @@
+/*
+ * 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.screenshot.appclips;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.window.ScreenCapture.ScreenshotHardwareBuffer;
+import android.window.ScreenCapture.ScreenshotSync;
+
+import androidx.annotation.Nullable;
+
+import com.android.systemui.screenshot.AppClipsActivity;
+import com.android.wm.shell.bubbles.Bubbles;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+/**
+ * A helper service that runs in SysUI process and helps {@link AppClipsActivity} which runs in its
+ * own separate process take a screenshot.
+ */
+public class AppClipsScreenshotHelperService extends Service {
+
+ private final Optional<Bubbles> mOptionalBubbles;
+
+ @Inject
+ public AppClipsScreenshotHelperService(Optional<Bubbles> optionalBubbles) {
+ mOptionalBubbles = optionalBubbles;
+ }
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new IAppClipsScreenshotHelperService.Stub() {
+ @Override
+ @Nullable
+ public ScreenshotHardwareBufferInternal takeScreenshot(int displayId) {
+ if (mOptionalBubbles.isEmpty()) {
+ return null;
+ }
+
+ ScreenshotSync screenshotSync =
+ mOptionalBubbles.get().getScreenshotExcludingBubble(displayId);
+ ScreenshotHardwareBuffer screenshotHardwareBuffer = screenshotSync.get();
+ if (screenshotHardwareBuffer == null) {
+ return null;
+ }
+
+ return new ScreenshotHardwareBufferInternal(screenshotHardwareBuffer);
+ }
+ };
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
new file mode 100644
index 0000000..d0b7ad3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
@@ -0,0 +1,110 @@
+/*
+ * 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.screenshot.appclips;
+
+import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
+
+import android.app.Activity;
+import android.app.Service;
+import android.app.StatusBarManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.IBinder;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.statusbar.IAppClipsService;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.wm.shell.bubbles.Bubbles;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+/**
+ * A service that communicates with {@link StatusBarManager} to support the
+ * {@link StatusBarManager#canLaunchCaptureContentActivityForNote(Activity)} API.
+ */
+public class AppClipsService extends Service {
+
+ @Application private final Context mContext;
+ private final FeatureFlags mFeatureFlags;
+ private final Optional<Bubbles> mOptionalBubbles;
+ private final DevicePolicyManager mDevicePolicyManager;
+ private final boolean mAreTaskAndTimeIndependentPrerequisitesMet;
+
+ @Inject
+ public AppClipsService(@Application Context context, FeatureFlags featureFlags,
+ Optional<Bubbles> optionalBubbles, DevicePolicyManager devicePolicyManager) {
+ mContext = context;
+ mFeatureFlags = featureFlags;
+ mOptionalBubbles = optionalBubbles;
+ mDevicePolicyManager = devicePolicyManager;
+
+ mAreTaskAndTimeIndependentPrerequisitesMet = checkIndependentVariables();
+ }
+
+ private boolean checkIndependentVariables() {
+ if (!mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)) {
+ return false;
+ }
+
+ if (mOptionalBubbles.isEmpty()) {
+ return false;
+ }
+
+ return isComponentValid();
+ }
+
+ private boolean isComponentValid() {
+ ComponentName componentName;
+ try {
+ componentName = ComponentName.unflattenFromString(
+ mContext.getString(R.string.config_screenshotAppClipsActivityComponent));
+ } catch (Resources.NotFoundException e) {
+ return false;
+ }
+
+ return componentName != null
+ && !componentName.getPackageName().isEmpty()
+ && !componentName.getClassName().isEmpty();
+ }
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new IAppClipsService.Stub() {
+ @Override
+ public boolean canLaunchCaptureContentActivityForNote(int taskId) {
+ if (!mAreTaskAndTimeIndependentPrerequisitesMet) {
+ return false;
+ }
+
+ if (!mOptionalBubbles.get().isAppBubbleTaskId(taskId)) {
+ return false;
+ }
+
+ return !mDevicePolicyManager.getScreenCaptureDisabled(null);
+ }
+ };
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
new file mode 100644
index 0000000..4759cc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
@@ -0,0 +1,233 @@
+/*
+ * 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.screenshot;
+
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
+import static android.content.Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE;
+import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
+
+import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
+
+import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.ResultReceiver;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.notetask.NoteTaskController;
+import com.android.wm.shell.bubbles.Bubbles;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+/**
+ * A trampoline activity that is responsible for:
+ * <ul>
+ * <li>Performing precondition checks before starting the actual screenshot activity.
+ * <li>Communicating with the screenshot activity and the calling activity.
+ * </ul>
+ *
+ * <p>As this activity is started in a bubble app, the windowing for this activity is restricted
+ * to the parent bubble app. The screenshot editing activity, see {@link AppClipsActivity}, is
+ * started in a regular activity window using {@link Intent#FLAG_ACTIVITY_NEW_TASK}. However,
+ * {@link Activity#startActivityForResult(Intent, int)} is not compatible with
+ * {@link Intent#FLAG_ACTIVITY_NEW_TASK}. So, this activity acts as a trampoline activity to
+ * abstract the complexity of communication with the screenshot editing activity for a simpler
+ * developer experience.
+ *
+ * TODO(b/267309532): Polish UI and animations.
+ */
+public class AppClipsTrampolineActivity extends Activity {
+
+ private static final String TAG = AppClipsTrampolineActivity.class.getSimpleName();
+ public static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+ public static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI";
+ public static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE";
+ static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER";
+
+ private final DevicePolicyManager mDevicePolicyManager;
+ private final FeatureFlags mFeatureFlags;
+ private final Optional<Bubbles> mOptionalBubbles;
+ private final NoteTaskController mNoteTaskController;
+ private final ResultReceiver mResultReceiver;
+
+ private Intent mKillAppClipsBroadcastIntent;
+
+ @Inject
+ public AppClipsTrampolineActivity(DevicePolicyManager devicePolicyManager, FeatureFlags flags,
+ Optional<Bubbles> optionalBubbles, NoteTaskController noteTaskController,
+ @Main Handler mainHandler) {
+ mDevicePolicyManager = devicePolicyManager;
+ mFeatureFlags = flags;
+ mOptionalBubbles = optionalBubbles;
+ mNoteTaskController = noteTaskController;
+
+ mResultReceiver = createResultReceiver(mainHandler);
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState != null) {
+ return;
+ }
+
+ if (!mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)) {
+ finish();
+ return;
+ }
+
+ if (mOptionalBubbles.isEmpty()) {
+ setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ return;
+ }
+
+ if (!mOptionalBubbles.get().isAppBubbleTaskId(getTaskId())) {
+ setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED);
+ return;
+ }
+
+ if (mDevicePolicyManager.getScreenCaptureDisabled(null)) {
+ setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN);
+ return;
+ }
+
+ ComponentName componentName;
+ try {
+ componentName = ComponentName.unflattenFromString(
+ getString(R.string.config_screenshotAppClipsActivityComponent));
+ } catch (Resources.NotFoundException e) {
+ setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ return;
+ }
+
+ if (componentName == null || componentName.getPackageName().isEmpty()
+ || componentName.getClassName().isEmpty()) {
+ setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ return;
+ }
+
+ Intent intent = new Intent().setComponent(componentName).addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK).putExtra(EXTRA_RESULT_RECEIVER, mResultReceiver);
+ try {
+ // Start the App Clips activity.
+ startActivity(intent);
+
+ // Set up the broadcast intent that will inform the above App Clips activity to finish
+ // when this trampoline activity is finished.
+ mKillAppClipsBroadcastIntent =
+ new Intent(ACTION_FINISH_FROM_TRAMPOLINE)
+ .setComponent(componentName)
+ .setPackage(componentName.getPackageName());
+ } catch (ActivityNotFoundException e) {
+ setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ if (isFinishing() && mKillAppClipsBroadcastIntent != null) {
+ sendBroadcast(mKillAppClipsBroadcastIntent, PERMISSION_SELF);
+ }
+ }
+
+ private void setErrorResultAndFinish(int errorCode) {
+ setResult(RESULT_OK,
+ new Intent().putExtra(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, errorCode));
+ finish();
+ }
+
+ private class AppClipsResultReceiver extends ResultReceiver {
+
+ AppClipsResultReceiver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (isFinishing()) {
+ // It's too late, trampoline activity is finishing or already finished.
+ // Return early.
+ return;
+ }
+
+ // Package the response that should be sent to the calling activity.
+ Intent convertedData = new Intent();
+ int statusCode = CAPTURE_CONTENT_FOR_NOTE_FAILED;
+ if (resultData != null) {
+ statusCode = resultData.getInt(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE,
+ CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ }
+ convertedData.putExtra(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, statusCode);
+
+ if (statusCode == CAPTURE_CONTENT_FOR_NOTE_SUCCESS) {
+ Uri uri = resultData.getParcelable(EXTRA_SCREENSHOT_URI, Uri.class);
+ convertedData.setData(uri).addFlags(FLAG_GRANT_READ_URI_PERMISSION);
+ }
+
+ // Broadcast no longer required, setting it to null.
+ mKillAppClipsBroadcastIntent = null;
+
+ // Expand the note bubble before returning the result. As App Clips API is only
+ // available when in a bubble, isInMultiWindowMode is always false below.
+ mNoteTaskController.showNoteTask(false);
+ setResult(RESULT_OK, convertedData);
+ finish();
+ }
+ }
+
+ /**
+ * @return a {@link ResultReceiver} by initializing an {@link AppClipsResultReceiver} and
+ * converting it into a generic {@link ResultReceiver} to pass across a different but trusted
+ * process.
+ */
+ private ResultReceiver createResultReceiver(@Main Handler handler) {
+ AppClipsResultReceiver appClipsResultReceiver = new AppClipsResultReceiver(handler);
+ Parcel parcel = Parcel.obtain();
+ appClipsResultReceiver.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ ResultReceiver resultReceiver = ResultReceiver.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+ return resultReceiver;
+ }
+
+ /** This is a test only API for mocking response from {@link AppClipsActivity}. */
+ @VisibleForTesting
+ public ResultReceiver getResultReceiverForTest() {
+ return mResultReceiver;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
new file mode 100644
index 0000000..5a7b5f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
@@ -0,0 +1,184 @@
+/*
+ * 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.screenshot;
+
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.HardwareRenderer;
+import android.graphics.RecordingCanvas;
+import android.graphics.Rect;
+import android.graphics.RenderNode;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Process;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.screenshot.appclips.AppClipsCrossProcessHelper;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.time.ZonedDateTime;
+import java.util.UUID;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/** A {@link ViewModel} to help with the App Clips screenshot flow. */
+final class AppClipsViewModel extends ViewModel {
+
+ private final AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
+ private final ImageExporter mImageExporter;
+ @Main
+ private final Executor mMainExecutor;
+ @Background
+ private final Executor mBgExecutor;
+
+ private final MutableLiveData<Bitmap> mScreenshotLiveData;
+ private final MutableLiveData<Uri> mResultLiveData;
+ private final MutableLiveData<Integer> mErrorLiveData;
+
+ AppClipsViewModel(AppClipsCrossProcessHelper appClipsCrossProcessHelper,
+ ImageExporter imageExporter, @Main Executor mainExecutor,
+ @Background Executor bgExecutor) {
+ mAppClipsCrossProcessHelper = appClipsCrossProcessHelper;
+ mImageExporter = imageExporter;
+ mMainExecutor = mainExecutor;
+ mBgExecutor = bgExecutor;
+
+ mScreenshotLiveData = new MutableLiveData<>();
+ mResultLiveData = new MutableLiveData<>();
+ mErrorLiveData = new MutableLiveData<>();
+ }
+
+ /** Grabs a screenshot and updates the {@link Bitmap} set in screenshot {@link LiveData}. */
+ void performScreenshot() {
+ mBgExecutor.execute(() -> {
+ Bitmap screenshot = mAppClipsCrossProcessHelper.takeScreenshot();
+ mMainExecutor.execute(() -> {
+ if (screenshot == null) {
+ mErrorLiveData.setValue(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ } else {
+ mScreenshotLiveData.setValue(screenshot);
+ }
+ });
+ });
+ }
+
+ /** Returns a {@link LiveData} that holds the captured screenshot. */
+ LiveData<Bitmap> getScreenshot() {
+ return mScreenshotLiveData;
+ }
+
+ /** Returns a {@link LiveData} that holds the {@link Uri} where screenshot is saved. */
+ LiveData<Uri> getResultLiveData() {
+ return mResultLiveData;
+ }
+
+ /**
+ * Returns a {@link LiveData} that holds the error codes for
+ * {@link Intent#EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE}.
+ */
+ LiveData<Integer> getErrorLiveData() {
+ return mErrorLiveData;
+ }
+
+ /**
+ * Saves the provided {@link Drawable} to storage then informs the result {@link Uri} to
+ * {@link LiveData}.
+ */
+ void saveScreenshotThenFinish(Drawable screenshotDrawable, Rect bounds) {
+ mBgExecutor.execute(() -> {
+ // Render the screenshot bitmap in background.
+ Bitmap screenshotBitmap = renderBitmap(screenshotDrawable, bounds);
+
+ // Export and save the screenshot in background.
+ // TODO(b/267310185): Save to work profile UserHandle.
+ ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export(
+ mBgExecutor, UUID.randomUUID(), screenshotBitmap, ZonedDateTime.now(),
+ Process.myUserHandle());
+
+ // Get the result and update state on main thread.
+ exportFuture.addListener(() -> {
+ try {
+ ImageExporter.Result result = exportFuture.get();
+ if (result.uri == null) {
+ mErrorLiveData.setValue(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ return;
+ }
+
+ mResultLiveData.setValue(result.uri);
+ } catch (CancellationException | InterruptedException | ExecutionException e) {
+ mErrorLiveData.setValue(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ }
+ }, mMainExecutor);
+ });
+ }
+
+ private static Bitmap renderBitmap(Drawable drawable, Rect bounds) {
+ final RenderNode output = new RenderNode("Screenshot save");
+ output.setPosition(0, 0, bounds.width(), bounds.height());
+ RecordingCanvas canvas = output.beginRecording();
+ canvas.translate(-bounds.left, -bounds.top);
+ canvas.clipRect(bounds);
+ drawable.draw(canvas);
+ output.endRecording();
+ return HardwareRenderer.createHardwareBitmap(output, bounds.width(), bounds.height());
+ }
+
+ /** Helper factory to help with injecting {@link AppClipsViewModel}. */
+ static final class Factory implements ViewModelProvider.Factory {
+
+ private final AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
+ private final ImageExporter mImageExporter;
+ @Main
+ private final Executor mMainExecutor;
+ @Background
+ private final Executor mBgExecutor;
+
+ @Inject
+ Factory(AppClipsCrossProcessHelper appClipsCrossProcessHelper, ImageExporter imageExporter,
+ @Main Executor mainExecutor, @Background Executor bgExecutor) {
+ mAppClipsCrossProcessHelper = appClipsCrossProcessHelper;
+ mImageExporter = imageExporter;
+ mMainExecutor = mainExecutor;
+ mBgExecutor = bgExecutor;
+ }
+
+ @NonNull
+ @Override
+ public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
+ if (modelClass != AppClipsViewModel.class) {
+ throw new IllegalArgumentException();
+ }
+
+ //noinspection unchecked
+ return (T) new AppClipsViewModel(mAppClipsCrossProcessHelper, mImageExporter,
+ mMainExecutor, mBgExecutor);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/IAppClipsScreenshotHelperService.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/IAppClipsScreenshotHelperService.aidl
new file mode 100644
index 0000000..640e742
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/IAppClipsScreenshotHelperService.aidl
@@ -0,0 +1,29 @@
+/**
+ * Copyright (C) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.appclips;
+
+import android.os.Bundle;
+
+import com.android.systemui.screenshot.appclips.ScreenshotHardwareBufferInternal;
+
+/**
+ * A helper service that runs in SysUI process and helps {@link AppClipsActivity} which runs in its
+ * own separate process take a screenshot.
+ */
+interface IAppClipsScreenshotHelperService {
+ @nullable ScreenshotHardwareBufferInternal takeScreenshot(in int displayId);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/ScreenshotHardwareBufferInternal.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/ScreenshotHardwareBufferInternal.aidl
new file mode 100644
index 0000000..3a7b944
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/ScreenshotHardwareBufferInternal.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (C) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.appclips;
+
+parcelable ScreenshotHardwareBufferInternal;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/ScreenshotHardwareBufferInternal.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/ScreenshotHardwareBufferInternal.java
new file mode 100644
index 0000000..3b107f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/ScreenshotHardwareBufferInternal.java
@@ -0,0 +1,96 @@
+/*
+ * 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.screenshot.appclips;
+
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.ParcelableColorSpace;
+import android.hardware.HardwareBuffer;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.window.ScreenCapture.ScreenshotHardwareBuffer;
+
+/**
+ * An internal version of {@link ScreenshotHardwareBuffer} that helps with parceling the information
+ * necessary for creating a {@link Bitmap}.
+ */
+public class ScreenshotHardwareBufferInternal implements Parcelable {
+
+ public static final Creator<ScreenshotHardwareBufferInternal> CREATOR =
+ new Creator<>() {
+ @Override
+ public ScreenshotHardwareBufferInternal createFromParcel(Parcel in) {
+ return new ScreenshotHardwareBufferInternal(in);
+ }
+
+ @Override
+ public ScreenshotHardwareBufferInternal[] newArray(int size) {
+ return new ScreenshotHardwareBufferInternal[size];
+ }
+ };
+ private final HardwareBuffer mHardwareBuffer;
+ private final ParcelableColorSpace mParcelableColorSpace;
+
+ public ScreenshotHardwareBufferInternal(
+ ScreenshotHardwareBuffer screenshotHardwareBuffer) {
+ mHardwareBuffer = screenshotHardwareBuffer.getHardwareBuffer();
+ mParcelableColorSpace = new ParcelableColorSpace(
+ screenshotHardwareBuffer.getColorSpace());
+ }
+
+ private ScreenshotHardwareBufferInternal(Parcel in) {
+ mHardwareBuffer = in.readParcelable(HardwareBuffer.class.getClassLoader(),
+ HardwareBuffer.class);
+ mParcelableColorSpace = in.readParcelable(ParcelableColorSpace.class.getClassLoader(),
+ ParcelableColorSpace.class);
+ }
+
+ /**
+ * Returns a {@link Bitmap} represented by the underlying data and successively closes the
+ * internal {@link HardwareBuffer}. See,
+ * {@link Bitmap#wrapHardwareBuffer(HardwareBuffer, ColorSpace)} and
+ * {@link HardwareBuffer#close()} for more information.
+ */
+ public Bitmap createBitmapThenCloseBuffer() {
+ Bitmap bitmap = Bitmap.wrapHardwareBuffer(mHardwareBuffer,
+ mParcelableColorSpace.getColorSpace());
+ mHardwareBuffer.close();
+ return bitmap;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mHardwareBuffer, flags);
+ dest.writeParcelable(mParcelableColorSpace, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ScreenshotHardwareBufferInternal)) {
+ return false;
+ }
+
+ ScreenshotHardwareBufferInternal other = (ScreenshotHardwareBufferInternal) obj;
+ return mHardwareBuffer.equals(other.mHardwareBuffer) && mParcelableColorSpace.equals(
+ other.mParcelableColorSpace);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index fdb0100..22e238c0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -24,6 +24,8 @@
import com.android.systemui.screenshot.ScreenshotPolicyImpl;
import com.android.systemui.screenshot.ScreenshotProxyService;
import com.android.systemui.screenshot.TakeScreenshotService;
+import com.android.systemui.screenshot.appclips.AppClipsScreenshotHelperService;
+import com.android.systemui.screenshot.appclips.AppClipsService;
import dagger.Binds;
import dagger.Module;
@@ -52,4 +54,13 @@
@Binds
abstract ImageCapture bindImageCaptureImpl(ImageCaptureImpl capture);
+ @Binds
+ @IntoMap
+ @ClassKey(AppClipsScreenshotHelperService.class)
+ abstract Service bindAppClipsScreenshotHelperService(AppClipsScreenshotHelperService service);
+
+ @Binds
+ @IntoMap
+ @ClassKey(AppClipsService.class)
+ abstract Service bindAppClipsService(AppClipsService service);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 479510a..2ac7f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -3151,17 +3151,11 @@
}
// The padding on this area is large enough that we can use a cheaper clipping strategy
mKeyguardStatusViewController.setClipBounds(clipStatusView ? mLastQsClipBounds : null);
- if (!qsVisible && mSplitShadeEnabled) {
- // On the lockscreen when qs isn't visible, we don't want the bounds of the shade to
- // be visible, otherwise you can see the bounds once swiping up to see bouncer
- mScrimController.setNotificationsBounds(0, 0, 0, 0);
- } else {
- // Increase the height of the notifications scrim when not in split shade
- // (e.g. portrait tablet) so the rounded corners are not visible at the bottom,
- // in this case they are rendered off-screen
- final int notificationsScrimBottom = mSplitShadeEnabled ? bottom : bottom + radius;
- mScrimController.setNotificationsBounds(left, top, right, notificationsScrimBottom);
- }
+ // Increase the height of the notifications scrim when not in split shade
+ // (e.g. portrait tablet) so the rounded corners are not visible at the bottom,
+ // in this case they are rendered off-screen
+ final int notificationsScrimBottom = mSplitShadeEnabled ? bottom : bottom + radius;
+ mScrimController.setNotificationsBounds(left, top, right, notificationsScrimBottom);
if (mSplitShadeEnabled) {
mKeyguardStatusBarViewController.setNoTopClipping();
@@ -3222,6 +3216,12 @@
private int calculateQsBottomPosition(float qsExpansionFraction) {
if (mTransitioningToFullShadeProgress > 0.0f) {
return mTransitionToFullShadeQSPosition;
+ } else if (mSplitShadeEnabled) {
+ // in split shade - outside lockscreen transition handled above - we simply jump between
+ // two qs expansion values - either shade is closed and qs expansion is 0 or shade is
+ // open and qs expansion is 1
+ int qsBottomTarget = mQs.getDesiredHeight() + mLargeScreenShadeHeaderHeight;
+ return qsExpansionFraction > 0 ? qsBottomTarget : 0;
} else {
int qsBottomYFrom = (int) getHeaderTranslation() + mQs.getQsMinExpansionHeight();
int expandedTopMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight : 0;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/OWNERS b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
index d71fbf6..c8b6a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
@@ -7,6 +7,7 @@
per-file NotificationsQuickSettingsContainer.java = kozynski@google.com, asc@google.com
per-file NotificationsQSContainerController.kt = kozynski@google.com, asc@google.com
per-file *ShadeHeader* = kozynski@google.com, asc@google.com
+per-file *Shade* = justinweir@google.com
per-file NotificationShadeWindowViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com
per-file NotificationShadeWindowView.java = pixel@google.com, cinek@google.com, juliacr@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
index 393279b..641131e 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
@@ -44,6 +44,11 @@
const val DREAM_SMARTSPACE_PRECONDITION = "dream_smartspace_precondition"
/**
+ * The BcSmartspaceDataPlugin for the standalone date (+alarm+dnd).
+ */
+ const val DATE_SMARTSPACE_DATA_PLUGIN = "date_smartspace_data_plugin"
+
+ /**
* The BcSmartspaceDataPlugin for the standalone weather.
*/
const val WEATHER_SMARTSPACE_DATA_PLUGIN = "weather_smartspace_data_plugin"
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 5b62d30..02d0f94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -52,12 +52,15 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.regionsampling.RegionSampler
import com.android.systemui.shared.regionsampling.UpdateColorCallback
+import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DATE_SMARTSPACE_DATA_PLUGIN
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.WEATHER_SMARTSPACE_DATA_PLUGIN
+import com.android.systemui.statusbar.Weather
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.util.concurrency.Execution
import com.android.systemui.util.settings.SecureSettings
+import java.time.Instant
import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -84,6 +87,8 @@
@Main private val uiExecutor: Executor,
@Background private val bgExecutor: Executor,
@Main private val handler: Handler,
+ @Named(DATE_SMARTSPACE_DATA_PLUGIN)
+ optionalDatePlugin: Optional<BcSmartspaceDataPlugin>,
@Named(WEATHER_SMARTSPACE_DATA_PLUGIN)
optionalWeatherPlugin: Optional<BcSmartspaceDataPlugin>,
optionalPlugin: Optional<BcSmartspaceDataPlugin>,
@@ -94,6 +99,7 @@
}
private var session: SmartspaceSession? = null
+ private val datePlugin: BcSmartspaceDataPlugin? = optionalDatePlugin.orElse(null)
private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null)
private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
private val configPlugin: BcSmartspaceConfigPlugin? = optionalConfigPlugin.orElse(null)
@@ -161,8 +167,19 @@
}
isContentUpdatedOnce = true
}
+
+ val now = Instant.now()
+ val weatherTarget = targets.find { t ->
+ t.featureType == SmartspaceTarget.FEATURE_WEATHER &&
+ now.isAfter(Instant.ofEpochMilli(t.creationTimeMillis)) &&
+ now.isBefore(Instant.ofEpochMilli(t.expiryTimeMillis))
+ }
+ if (weatherTarget != null) {
+ val weatherData = Weather.fromBundle(weatherTarget.baseAction.extras)
+ }
}
+
private val userTrackerCallback = object : UserTracker.Callback {
override fun onUserChanged(newUser: Int, userContext: Context) {
execution.assertIsMainThread()
@@ -223,7 +240,7 @@
execution.assertIsMainThread()
return featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED) &&
- weatherPlugin != null
+ datePlugin != null && weatherPlugin != null
}
private fun updateBypassEnabled() {
@@ -232,6 +249,25 @@
}
/**
+ * Constructs the date view and connects it to the smartspace service.
+ */
+ fun buildAndConnectDateView(parent: ViewGroup): View? {
+ execution.assertIsMainThread()
+
+ if (!isEnabled()) {
+ throw RuntimeException("Cannot build view when not enabled")
+ }
+ if (!isDateWeatherDecoupled()) {
+ throw RuntimeException("Cannot build date view when not decoupled")
+ }
+
+ val view = buildView(parent, datePlugin)
+ connectSession()
+
+ return view
+ }
+
+ /**
* Constructs the weather view and connects it to the smartspace service.
*/
fun buildAndConnectWeatherView(parent: ViewGroup): View? {
@@ -309,7 +345,7 @@
}
private fun connectSession() {
- if (weatherPlugin == null && plugin == null) return
+ if (datePlugin == null && weatherPlugin == null && plugin == null) return
if (session != null || smartspaceViews.isEmpty()) {
return
}
@@ -347,6 +383,7 @@
statusBarStateController.addCallback(statusBarStateListener)
bypassController.registerOnBypassStateChangedListener(bypassStateChangedListener)
+ datePlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
@@ -384,6 +421,8 @@
bypassController.unregisterOnBypassStateChangedListener(bypassStateChangedListener)
session = null
+ datePlugin?.registerSmartspaceEventNotifier(null)
+
weatherPlugin?.registerSmartspaceEventNotifier(null)
weatherPlugin?.onTargetsAvailable(emptyList())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
index 0c95eab..058042c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
@@ -71,9 +71,11 @@
* marking them as relevant for setup are allowed to show when device is unprovisioned
*/
private boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
- // system_server checks the permission so systemui can just check whether the
- // extra exists
- return sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
+ final boolean hasPermission = checkUidPermission(
+ Manifest.permission.NOTIFICATION_DURING_SETUP,
+ sbn.getUid()) == PackageManager.PERMISSION_GRANTED;
+ return hasPermission
+ && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
}
private int checkUidPermission(String permission, int uid) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9275e2b..a6b71dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -591,6 +591,7 @@
}
mShowingPublicInitialized = false;
updateNotificationColor();
+ updateLongClickable();
if (mMenuRow != null) {
mMenuRow.onNotificationUpdated(mEntry.getSbn());
mMenuRow.setAppName(mAppName);
@@ -1196,8 +1197,26 @@
return getShowingLayout().getVisibleWrapper();
}
+ private boolean isNotificationRowLongClickable() {
+ if (mLongPressListener == null) {
+ return false;
+ }
+
+ if (!areGutsExposed()) { // guts is not opened
+ return true;
+ }
+
+ // if it is leave behind, it shouldn't be long clickable.
+ return !isGutsLeaveBehind();
+ }
+
+ private void updateLongClickable() {
+ setLongClickable(isNotificationRowLongClickable());
+ }
+
public void setLongPressListener(LongPressListener longPressListener) {
mLongPressListener = longPressListener;
+ updateLongClickable();
}
public void setDragController(ExpandableNotificationRowDragController dragController) {
@@ -2044,11 +2063,13 @@
void onGutsOpened() {
resetTranslation();
updateContentAccessibilityImportanceForGuts(false /* isEnabled */);
+ updateLongClickable();
}
void onGutsClosed() {
updateContentAccessibilityImportanceForGuts(true /* isEnabled */);
mIsSnoozed = false;
+ updateLongClickable();
}
/**
@@ -2947,6 +2968,10 @@
return (mGuts != null && mGuts.isExposed());
}
+ private boolean isGutsLeaveBehind() {
+ return (mGuts != null && mGuts.isLeavebehind());
+ }
+
@Override
public boolean isContentExpandable() {
if (mIsSummaryWithChildren && !shouldShowPublic()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 37ff11d..efcbb3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -586,7 +586,9 @@
}
final ExpandableNotificationRow row = (ExpandableNotificationRow) view;
- view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ if (view.isLongClickable()) {
+ view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ }
if (row.areGutsExposed()) {
closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
true /* removeControls */, -1 /* x */, -1 /* y */,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index aab36da..1fb7eb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -147,7 +147,7 @@
private boolean mShadeNeedsToClose = false;
@VisibleForTesting
- static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
+ static final float RUBBER_BAND_FACTOR_NORMAL = 0.1f;
private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index c248a50..b88531e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -352,7 +352,7 @@
}
private boolean willAnimateFromLockScreenToAod() {
- return getAlwaysOn() && mKeyguardVisible;
+ return shouldControlScreenOff() && mKeyguardVisible;
}
private boolean getBoolean(String propName, int resId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 344d233..c1c6c88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -23,6 +23,7 @@
import android.view.View;
import android.view.ViewStub;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.LockIconView;
import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterView;
@@ -67,6 +68,7 @@
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.CarrierConfigTracker;
import com.android.systemui.util.settings.SecureSettings;
@@ -299,7 +301,9 @@
OperatorNameViewController.Factory operatorNameViewControllerFactory,
SecureSettings secureSettings,
@Main Executor mainExecutor,
- DumpManager dumpManager
+ DumpManager dumpManager,
+ StatusBarWindowStateController statusBarWindowStateController,
+ KeyguardUpdateMonitor keyguardUpdateMonitor
) {
return new CollapsedStatusBarFragment(statusBarFragmentComponentFactory,
ongoingCallController,
@@ -320,7 +324,9 @@
operatorNameViewControllerFactory,
secureSettings,
mainExecutor,
- dumpManager);
+ dumpManager,
+ statusBarWindowStateController,
+ keyguardUpdateMonitor);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index cbd27cf..65becf7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -44,6 +44,7 @@
import androidx.annotation.VisibleForTesting;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
@@ -72,6 +73,8 @@
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
import com.android.systemui.util.CarrierConfigTracker;
import com.android.systemui.util.CarrierConfigTracker.CarrierConfigChangedListener;
import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener;
@@ -129,6 +132,8 @@
private final SecureSettings mSecureSettings;
private final Executor mMainExecutor;
private final DumpManager mDumpManager;
+ private final StatusBarWindowStateController mStatusBarWindowStateController;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private List<String> mBlockedIcons = new ArrayList<>();
private Map<Startable, Startable.State> mStartableStates = new ArrayMap<>();
@@ -164,6 +169,22 @@
}
};
+ /**
+ * Whether we've launched the secure camera over the lockscreen, but haven't yet received a
+ * status bar window state change afterward.
+ *
+ * We wait for this state change (which will tell us whether to show/hide the status bar icons)
+ * so that there is no flickering/jump cutting during the camera launch.
+ */
+ private boolean mWaitingForWindowStateChangeAfterCameraLaunch = false;
+
+ /**
+ * Listener that updates {@link #mWaitingForWindowStateChangeAfterCameraLaunch} when it receives
+ * a new status bar window state.
+ */
+ private final StatusBarWindowStateListener mStatusBarWindowStateListener = state ->
+ mWaitingForWindowStateChangeAfterCameraLaunch = false;
+
@SuppressLint("ValidFragment")
public CollapsedStatusBarFragment(
StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory,
@@ -185,7 +206,9 @@
OperatorNameViewController.Factory operatorNameViewControllerFactory,
SecureSettings secureSettings,
@Main Executor mainExecutor,
- DumpManager dumpManager
+ DumpManager dumpManager,
+ StatusBarWindowStateController statusBarWindowStateController,
+ KeyguardUpdateMonitor keyguardUpdateMonitor
) {
mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
mOngoingCallController = ongoingCallController;
@@ -207,6 +230,20 @@
mSecureSettings = secureSettings;
mMainExecutor = mainExecutor;
mDumpManager = dumpManager;
+ mStatusBarWindowStateController = statusBarWindowStateController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mStatusBarWindowStateController.addListener(mStatusBarWindowStateListener);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mStatusBarWindowStateController.removeListener(mStatusBarWindowStateListener);
}
@Override
@@ -254,6 +291,11 @@
mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
}
+ @Override
+ public void onCameraLaunchGestureDetected(int source) {
+ mWaitingForWindowStateChangeAfterCameraLaunch = true;
+ }
+
@VisibleForTesting
void updateBlockedIcons() {
mBlockedIcons.clear();
@@ -466,6 +508,27 @@
&& mNotificationPanelViewController.hideStatusBarIconsWhenExpanded()) {
return true;
}
+
+ // When launching the camera over the lockscreen, the icons become visible momentarily
+ // before animating out, since we're not yet aware that the launching camera activity is
+ // fullscreen. Even once the activity finishes launching, it takes a short time before WM
+ // decides that the top app wants to hide the icons and tells us to hide them. To ensure
+ // that this high-visibility animation is smooth, keep the icons hidden during a camera
+ // launch until we receive a window state change which indicates that the activity is done
+ // launching and WM has decided to show/hide the icons. For extra safety (to ensure the
+ // icons don't remain hidden somehow) we double check that the camera is still showing, the
+ // status bar window isn't hidden, and we're still occluded as well, though these checks
+ // are typically unnecessary.
+ final boolean hideIconsForSecureCamera =
+ (mWaitingForWindowStateChangeAfterCameraLaunch ||
+ !mStatusBarWindowStateController.windowIsShowing()) &&
+ mKeyguardUpdateMonitor.isSecureCameraLaunchedOverKeyguard() &&
+ mKeyguardStateController.isOccluded();
+
+ if (hideIconsForSecureCamera) {
+ return true;
+ }
+
return mStatusBarHideIconsForBouncerManager.getShouldHideStatusBarIconsForBouncer();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 9ae38e9..0e4a432 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -28,6 +28,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.CarrierConfigTracker
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -37,10 +38,12 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
@@ -100,6 +103,7 @@
constructor(
private val mobileConnectionsRepo: MobileConnectionsRepository,
private val carrierConfigTracker: CarrierConfigTracker,
+ private val logger: ConnectivityPipelineLogger,
userSetupRepo: UserSetupRepository,
@Application private val scope: CoroutineScope,
) : MobileIconsInteractor {
@@ -168,6 +172,8 @@
}
}
}
+ .distinctUntilChanged()
+ .onEach { logger.logFilteredSubscriptionsChanged(it) }
override val defaultDataSubId = mobileConnectionsRepo.defaultDataSubId
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index 829a5ca..ef75713 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -23,6 +23,8 @@
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -30,7 +32,9 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -51,13 +55,17 @@
interactor: MobileIconsInteractor,
private val iconController: StatusBarIconController,
private val iconsViewModelFactory: MobileIconsViewModel.Factory,
+ private val logger: ConnectivityPipelineLogger,
@Application private val scope: CoroutineScope,
private val statusBarPipelineFlags: StatusBarPipelineFlags,
) : CoreStartable {
private val mobileSubIds: Flow<List<Int>> =
- interactor.filteredSubscriptions.mapLatest { subscriptions ->
- subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
- }
+ interactor.filteredSubscriptions
+ .mapLatest { subscriptions ->
+ subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
+ }
+ .distinctUntilChanged()
+ .onEach { logger.logUiAdapterSubIdsUpdated(it) }
/**
* We expose the list of tracked subscriptions as a flow of a list of ints, where each int is
@@ -72,6 +80,9 @@
/** In order to keep the logs tame, we will reuse the same top-level mobile icons view model */
val mobileIconsViewModel = iconsViewModelFactory.create(mobileSubIdsState)
+ private var isCollecting: Boolean = false
+ private var lastValue: List<Int>? = null
+
override fun start() {
// Only notify the icon controller if we want to *render* the new icons.
// Note that this flow may still run if
@@ -79,8 +90,18 @@
// get the logging data without rendering.
if (statusBarPipelineFlags.useNewMobileIcons()) {
scope.launch {
- mobileSubIds.collectLatest { iconController.setNewMobileIconSubIds(it) }
+ isCollecting = true
+ mobileSubIds.collectLatest {
+ logger.logUiAdapterSubIdsSentToIconController(it)
+ lastValue = it
+ iconController.setNewMobileIconSubIds(it)
+ }
}
}
}
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("isCollecting=$isCollecting")
+ pw.println("Last values sent to icon controller: $lastValue")
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index 3e81c7c..a4b2abc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -29,6 +29,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.R
+import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.StatusBarIconView
@@ -97,6 +98,12 @@
}
}
+ launch {
+ viewModel.contentDescription.distinctUntilChanged().collect {
+ ContentDescriptionViewBinder.bind(it, view)
+ }
+ }
+
// Set the network type icon
launch {
viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 5e935616..9e2024a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
+import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE
import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
@@ -43,6 +45,7 @@
val subscriptionId: Int
/** An int consumable by [SignalDrawable] for display */
val iconId: Flow<Int>
+ val contentDescription: Flow<ContentDescription>
val roaming: Flow<Boolean>
/** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
val networkTypeIcon: Flow<Icon?>
@@ -102,6 +105,23 @@
.stateIn(scope, SharingStarted.WhileSubscribed(), initial)
}
+ override val contentDescription: Flow<ContentDescription> = run {
+ val initial = ContentDescription.Resource(PHONE_SIGNAL_STRENGTH_NONE)
+ combine(
+ iconInteractor.level,
+ iconInteractor.isInService,
+ ) { level, isInService ->
+ val resId =
+ when {
+ isInService -> PHONE_SIGNAL_STRENGTH[level]
+ else -> PHONE_SIGNAL_STRENGTH_NONE
+ }
+ ContentDescription.Resource(resId)
+ }
+ .distinctUntilChanged()
+ .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
+ }
+
private val showNetworkTypeIcon: Flow<Boolean> =
combine(
iconInteractor.isDataConnected,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
index 491f3a5..7c7ffaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -25,6 +25,7 @@
import com.android.systemui.log.dagger.StatusBarConnectivityLog
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.toString
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -201,6 +202,35 @@
)
}
+ // TODO(b/238425913): We should split this class into mobile-specific and wifi-specific loggers.
+
+ fun logFilteredSubscriptionsChanged(subs: List<SubscriptionModel>) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ { str1 = subs.toString() },
+ { "Filtered subscriptions updated: $str1" },
+ )
+ }
+
+ fun logUiAdapterSubIdsUpdated(subs: List<Int>) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ { str1 = subs.toString() },
+ { "Sub IDs in MobileUiAdapter updated internally: $str1" },
+ )
+ }
+
+ fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ { str1 = subs.toString() },
+ { "Sub IDs in MobileUiAdapter being sent to icon controller: $str1" },
+ )
+ }
+
companion object {
const val SB_LOGGING_TAG = "SbConnectivity"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
index cc0ec54..b1e2812 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
@@ -77,6 +77,17 @@
return binding.getShouldIconBeVisible()
}
+ /** See [StatusBarIconView.getDrawingRect]. */
+ override fun getDrawingRect(outRect: Rect) {
+ super.getDrawingRect(outRect)
+ val translationX = translationX.toInt()
+ val translationY = translationY.toInt()
+ outRect.left += translationX
+ outRect.right += translationX
+ outRect.top += translationY
+ outRect.bottom += translationY
+ }
+
/**
* Initializes this view.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
index 60f6df6..8f424b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
@@ -61,6 +61,10 @@
listeners.add(listener)
}
+ fun removeListener(listener: StatusBarWindowStateListener) {
+ listeners.remove(listener)
+ }
+
/** Returns true if the window is currently showing. */
fun windowIsShowing() = windowState == WINDOW_STATE_SHOWING
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index ec6965a..899b0c2 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -80,6 +80,26 @@
)
}
+ /** Logs that there was a failure to animate the view in. */
+ fun logAnimateInFailure() {
+ buffer.log(
+ tag,
+ LogLevel.WARNING,
+ {},
+ { "View's appearance animation failed. Forcing view display manually." },
+ )
+ }
+
+ /** Logs that there was a failure to animate the view out. */
+ fun logAnimateOutFailure() {
+ buffer.log(
+ tag,
+ LogLevel.WARNING,
+ {},
+ { "View's disappearance animation failed." },
+ )
+ }
+
fun logViewHidden(info: T) {
buffer.log(
tag,
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt
new file mode 100644
index 0000000..01a81de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.temporarydisplay.chipbar
+
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.animation.ViewHierarchyAnimator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.util.children
+import javax.inject.Inject
+
+/**
+ * A class controlling chipbar animations. Typically delegates to [ViewHierarchyAnimator].
+ *
+ * Used so that animations can be mocked out in tests.
+ */
+@SysUISingleton
+open class ChipbarAnimator @Inject constructor() {
+ /**
+ * Animates [innerView] and its children into view.
+ *
+ * @return true if the animation was successfully started and false if the animation can't be
+ * run for any reason.
+ *
+ * See [ViewHierarchyAnimator.animateAddition].
+ */
+ open fun animateViewIn(innerView: ViewGroup, onAnimationEnd: Runnable): Boolean {
+ return ViewHierarchyAnimator.animateAddition(
+ innerView,
+ ViewHierarchyAnimator.Hotspot.TOP,
+ Interpolators.EMPHASIZED_DECELERATE,
+ duration = ANIMATION_IN_DURATION,
+ includeMargins = true,
+ includeFadeIn = true,
+ onAnimationEnd = onAnimationEnd,
+ )
+ }
+
+ /**
+ * Animates [innerView] and its children out of view.
+ *
+ * @return true if the animation was successfully started and false if the animation can't be
+ * run for any reason.
+ *
+ * See [ViewHierarchyAnimator.animateRemoval].
+ */
+ open fun animateViewOut(innerView: ViewGroup, onAnimationEnd: Runnable): Boolean {
+ return ViewHierarchyAnimator.animateRemoval(
+ innerView,
+ ViewHierarchyAnimator.Hotspot.TOP,
+ Interpolators.EMPHASIZED_ACCELERATE,
+ ANIMATION_OUT_DURATION,
+ includeMargins = true,
+ onAnimationEnd,
+ )
+ }
+
+ /** Force shows this view and all child views. Should be used in case [animateViewIn] fails. */
+ fun forceDisplayView(innerView: View) {
+ innerView.alpha = 1f
+ if (innerView is ViewGroup) {
+ innerView.children.forEach { forceDisplayView(it) }
+ }
+ }
+}
+
+private const val ANIMATION_IN_DURATION = 500L
+private const val ANIMATION_OUT_DURATION = 250L
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 46f13cc..696134c 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -32,8 +32,6 @@
import com.android.internal.widget.CachingIconView
import com.android.systemui.Gefingerpoken
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
-import com.android.systemui.animation.ViewHierarchyAnimator
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Text.Companion.loadText
@@ -78,6 +76,7 @@
configurationController: ConfigurationController,
dumpManager: DumpManager,
powerManager: PowerManager,
+ private val chipbarAnimator: ChipbarAnimator,
private val falsingManager: FalsingManager,
private val falsingCollector: FalsingCollector,
private val swipeChipbarAwayGestureHandler: SwipeChipbarAwayGestureHandler?,
@@ -206,23 +205,17 @@
}
override fun animateViewIn(view: ViewGroup) {
+ // We can only request focus once the animation finishes.
val onAnimationEnd = Runnable {
maybeGetAccessibilityFocus(view.getTag(INFO_TAG) as ChipbarInfo?, view)
}
- val added =
- ViewHierarchyAnimator.animateAddition(
- view.getInnerView(),
- ViewHierarchyAnimator.Hotspot.TOP,
- Interpolators.EMPHASIZED_DECELERATE,
- duration = ANIMATION_IN_DURATION,
- includeMargins = true,
- includeFadeIn = true,
- // We can only request focus once the animation finishes.
- onAnimationEnd = onAnimationEnd,
- )
- // If the view doesn't get animated, the [onAnimationEnd] runnable won't get run. So, just
- // run it immediately.
- if (!added) {
+ val animatedIn = chipbarAnimator.animateViewIn(view.getInnerView(), onAnimationEnd)
+
+ // If the view doesn't get animated, the [onAnimationEnd] runnable won't get run and the
+ // views would remain un-displayed. So, just force-set/run those items immediately.
+ if (!animatedIn) {
+ logger.logAnimateInFailure()
+ chipbarAnimator.forceDisplayView(view.getInnerView())
onAnimationEnd.run()
}
}
@@ -230,18 +223,11 @@
override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
val innerView = view.getInnerView()
innerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_NONE
- val removed =
- ViewHierarchyAnimator.animateRemoval(
- innerView,
- ViewHierarchyAnimator.Hotspot.TOP,
- Interpolators.EMPHASIZED_ACCELERATE,
- ANIMATION_OUT_DURATION,
- includeMargins = true,
- onAnimationEnd,
- )
+ val removed = chipbarAnimator.animateViewOut(innerView, onAnimationEnd)
// If the view doesn't get animated, the [onAnimationEnd] runnable won't get run. So, just
// run it immediately.
if (!removed) {
+ logger.logAnimateOutFailure()
onAnimationEnd.run()
}
@@ -299,8 +285,6 @@
}
}
-private const val ANIMATION_IN_DURATION = 500L
-private const val ANIMATION_OUT_DURATION = 250L
@IdRes private val INFO_TAG = R.id.tag_chipbar_info
private const val SWIPE_UP_GESTURE_REASON = "SWIPE_UP_GESTURE_DETECTED"
private const val TAG = "ChipbarCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 9f6e602..6b507ba 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -115,6 +115,7 @@
private final SecureSettings mSecureSettings;
private final Executor mMainExecutor;
private final Handler mBgHandler;
+ private final boolean mIsMonochromaticEnabled;
private final Context mContext;
private final boolean mIsMonetEnabled;
private final UserTracker mUserTracker;
@@ -356,14 +357,24 @@
};
@Inject
- public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher,
- @Background Handler bgHandler, @Main Executor mainExecutor,
- @Background Executor bgExecutor, ThemeOverlayApplier themeOverlayApplier,
- SecureSettings secureSettings, WallpaperManager wallpaperManager,
- UserManager userManager, DeviceProvisionedController deviceProvisionedController,
- UserTracker userTracker, DumpManager dumpManager, FeatureFlags featureFlags,
- @Main Resources resources, WakefulnessLifecycle wakefulnessLifecycle) {
+ public ThemeOverlayController(
+ Context context,
+ BroadcastDispatcher broadcastDispatcher,
+ @Background Handler bgHandler,
+ @Main Executor mainExecutor,
+ @Background Executor bgExecutor,
+ ThemeOverlayApplier themeOverlayApplier,
+ SecureSettings secureSettings,
+ WallpaperManager wallpaperManager,
+ UserManager userManager,
+ DeviceProvisionedController deviceProvisionedController,
+ UserTracker userTracker,
+ DumpManager dumpManager,
+ FeatureFlags featureFlags,
+ @Main Resources resources,
+ WakefulnessLifecycle wakefulnessLifecycle) {
mContext = context;
+ mIsMonochromaticEnabled = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEME);
mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
mDeviceProvisionedController = deviceProvisionedController;
mBroadcastDispatcher = broadcastDispatcher;
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 8cb4deb..e5ab473 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -17,8 +17,11 @@
package com.android.systemui.user.data.repository
+import android.app.IActivityManager
+import android.app.UserSwitchObserver
import android.content.Context
import android.content.pm.UserInfo
+import android.os.IRemoteCallback
import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
@@ -30,6 +33,8 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
import com.android.systemui.settings.UserTracker
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.util.settings.GlobalSettings
@@ -68,6 +73,9 @@
/** [UserInfo] of the currently-selected user. */
val selectedUserInfo: Flow<UserInfo>
+ /** Whether user switching is currently in progress. */
+ val userSwitchingInProgress: Flow<Boolean>
+
/** User ID of the last non-guest selected user. */
val lastSelectedNonGuestUserId: Int
@@ -108,6 +116,8 @@
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val globalSettings: GlobalSettings,
private val tracker: UserTracker,
+ private val activityManager: IActivityManager,
+ featureFlags: FeatureFlags,
) : UserRepository {
private val _userSwitcherSettings = MutableStateFlow(runBlocking { getSettings() })
@@ -129,6 +139,10 @@
private var _isGuestUserResetting: Boolean = false
override var isGuestUserResetting: Boolean = _isGuestUserResetting
+ private val _isUserSwitchingInProgress = MutableStateFlow(false)
+ override val userSwitchingInProgress: Flow<Boolean>
+ get() = _isUserSwitchingInProgress
+
override val isGuestUserCreationScheduled = AtomicBoolean()
override val isStatusBarUserChipEnabled: Boolean =
@@ -141,6 +155,9 @@
init {
observeSelectedUser()
observeUserSettings()
+ if (featureFlags.isEnabled(FACE_AUTH_REFACTOR)) {
+ observeUserSwitching()
+ }
}
override fun refreshUsers() {
@@ -166,6 +183,28 @@
return _userSwitcherSettings.value.isSimpleUserSwitcher
}
+ private fun observeUserSwitching() {
+ conflatedCallbackFlow {
+ val callback =
+ object : UserSwitchObserver() {
+ override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback) {
+ trySendWithFailureLogging(true, TAG, "userSwitching started")
+ }
+
+ override fun onUserSwitchComplete(newUserId: Int) {
+ trySendWithFailureLogging(false, TAG, "userSwitching completed")
+ }
+ }
+ activityManager.registerUserSwitchObserver(callback, TAG)
+ trySendWithFailureLogging(false, TAG, "initial value defaulting to false")
+ awaitClose { activityManager.unregisterUserSwitchObserver(callback) }
+ }
+ .onEach { _isUserSwitchingInProgress.value = it }
+ // TODO (b/262838215), Make this stateIn and initialize directly in field declaration
+ // once the flag is launched
+ .launchIn(applicationScope)
+ }
+
private fun observeSelectedUser() {
conflatedCallbackFlow {
fun send() {
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 2c1e681..ed2772a 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -159,6 +159,12 @@
android:enabled="false"
tools:replace="android:authorities"
android:grantUriPermissions="true" />
+
+ <activity
+ android:name="com.android.systemui.screenshot.appclips.AppClipsTrampolineActivityTest$AppClipsTrampolineActivityTestable"
+ android:exported="false"
+ android:permission="com.android.systemui.permission.SELF"
+ android:excludeFromRecents="true" />
</application>
<instrumentation android:name="android.testing.TestableInstrumentation"
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index b9c23d4..43a2017 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -32,10 +32,12 @@
import com.android.systemui.plugins.ClockEvents
import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockFaceEvents
+import com.android.systemui.plugins.ClockTickRate
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
@@ -72,7 +74,7 @@
@Mock private lateinit var animations: ClockAnimations
@Mock private lateinit var events: ClockEvents
@Mock private lateinit var clock: ClockController
- @Mock private lateinit var mainExecutor: Executor
+ @Mock private lateinit var mainExecutor: DelayableExecutor
@Mock private lateinit var bgExecutor: Executor
@Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var smallClockController: ClockFaceController
@@ -97,6 +99,8 @@
whenever(largeClockController.events).thenReturn(largeClockEvents)
whenever(clock.events).thenReturn(events)
whenever(clock.animations).thenReturn(animations)
+ whenever(smallClockEvents.tickRate).thenReturn(ClockTickRate.PER_MINUTE)
+ whenever(largeClockEvents.tickRate).thenReturn(ClockTickRate.PER_MINUTE)
repository = FakeKeyguardRepository()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index a4180fd..36b3f89 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -33,6 +33,7 @@
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
@@ -47,6 +48,7 @@
import com.android.systemui.plugins.ClockController;
import com.android.systemui.plugins.ClockEvents;
import com.android.systemui.plugins.ClockFaceController;
+import com.android.systemui.plugins.ClockFaceEvents;
import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.AnimatableClockView;
@@ -99,6 +101,8 @@
@Mock
private ClockEvents mClockEvents;
@Mock
+ private ClockFaceEvents mClockFaceEvents;
+ @Mock
DumpManager mDumpManager;
@Mock
ClockEventController mClockEventController;
@@ -118,6 +122,11 @@
@Mock
private LogBuffer mLogBuffer;
+ private final View mFakeDateView = (View) (new ViewGroup(mContext) {
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {}
+ });
+ private final View mFakeWeatherView = new View(mContext);
private final View mFakeSmartspaceView = new View(mContext);
private KeyguardClockSwitchController mController;
@@ -145,6 +154,8 @@
when(mLargeClockView.getContext()).thenReturn(getContext());
when(mView.isAttachedToWindow()).thenReturn(true);
+ when(mSmartspaceController.buildAndConnectDateView(any())).thenReturn(mFakeDateView);
+ when(mSmartspaceController.buildAndConnectWeatherView(any())).thenReturn(mFakeWeatherView);
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
mExecutor = new FakeExecutor(new FakeSystemClock());
mController = new KeyguardClockSwitchController(
@@ -168,6 +179,8 @@
when(mClockController.getLargeClock()).thenReturn(mLargeClockController);
when(mClockController.getSmallClock()).thenReturn(mSmallClockController);
when(mClockController.getEvents()).thenReturn(mClockEvents);
+ when(mSmallClockController.getEvents()).thenReturn(mClockFaceEvents);
+ when(mLargeClockController.getEvents()).thenReturn(mClockFaceEvents);
when(mClockController.getAnimations()).thenReturn(mClockAnimations);
when(mClockRegistry.createCurrentClock()).thenReturn(mClockController);
when(mClockEventController.getClock()).thenReturn(mClockController);
@@ -252,6 +265,19 @@
}
@Test
+ public void onLocaleListChanged_rebuildsSmartspaceViews_whenDecouplingEnabled() {
+ when(mSmartspaceController.isEnabled()).thenReturn(true);
+ when(mSmartspaceController.isDateWeatherDecoupled()).thenReturn(true);
+ mController.init();
+
+ mController.onLocaleListChanged();
+ // Should be called once on initial setup, then once again for locale change
+ verify(mSmartspaceController, times(2)).buildAndConnectDateView(mView);
+ verify(mSmartspaceController, times(2)).buildAndConnectWeatherView(mView);
+ verify(mSmartspaceController, times(2)).buildAndConnectView(mView);
+ }
+
+ @Test
public void testSmartspaceDisabledShowsKeyguardStatusArea() {
when(mSmartspaceController.isEnabled()).thenReturn(false);
mController.init();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index e918c1c..fc11148 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -21,6 +21,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
import static com.google.common.truth.Truth.assertThat;
@@ -88,6 +89,7 @@
import com.android.systemui.decor.PrivacyDotCornerDecorProviderImpl;
import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
import com.android.systemui.decor.RoundedCornerResDelegate;
+import com.android.systemui.log.ScreenDecorationsLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.settings.UserTracker;
@@ -219,11 +221,14 @@
mAuthController,
mStatusBarStateController,
mKeyguardUpdateMonitor,
- mExecutor));
+ mExecutor,
+ new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer"))));
mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings,
mTunerService, mUserTracker, mDisplayTracker, mDotViewController, mThreadFactory,
- mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory) {
+ mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
+ new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
+ mAuthController) {
@Override
public void start() {
super.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index aac0b51..3da7261 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -19,8 +19,6 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
-import static com.android.systemui.flags.Flags.A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS;
-
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -46,7 +44,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
-import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.util.settings.SecureSettings;
@@ -74,6 +71,7 @@
public MockitoRule mockito = MockitoJUnit.rule();
private Context mContextWrapper;
+ private WindowManager mWindowManager;
private AccessibilityManager mAccessibilityManager;
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private AccessibilityFloatingMenuController mController;
@@ -97,6 +95,7 @@
}
};
+ mWindowManager = mContext.getSystemService(WindowManager.class);
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mLastButtonTargets = Settings.Secure.getStringForUser(mContextWrapper.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
@@ -158,7 +157,8 @@
public void onKeyguardVisibilityChanged_showing_destroyWidget() {
enableAccessibilityFloatingMenuConfig();
mController = setUpController();
- mController.mFloatingMenu = new AccessibilityFloatingMenu(mContextWrapper, mSecureSettings);
+ mController.mFloatingMenu = new MenuViewLayerController(mContextWrapper, mWindowManager,
+ mAccessibilityManager, mSecureSettings);
captureKeyguardUpdateMonitorCallback();
mKeyguardCallback.onUserUnlocked();
@@ -184,7 +184,8 @@
final int fakeUserId = 1;
enableAccessibilityFloatingMenuConfig();
mController = setUpController();
- mController.mFloatingMenu = new AccessibilityFloatingMenu(mContextWrapper, mSecureSettings);
+ mController.mFloatingMenu = new MenuViewLayerController(mContextWrapper, mWindowManager,
+ mAccessibilityManager, mSecureSettings);
captureKeyguardUpdateMonitorCallback();
mKeyguardCallback.onUserSwitching(fakeUserId);
@@ -197,7 +198,8 @@
final int fakeUserId = 1;
enableAccessibilityFloatingMenuConfig();
mController = setUpController();
- mController.mFloatingMenu = new AccessibilityFloatingMenu(mContextWrapper, mSecureSettings);
+ mController.mFloatingMenu = new MenuViewLayerController(mContextWrapper, mWindowManager,
+ mAccessibilityManager, mSecureSettings);
captureKeyguardUpdateMonitorCallback();
mKeyguardCallback.onUserUnlocked();
mKeyguardCallback.onKeyguardVisibilityChanged(true);
@@ -318,40 +320,18 @@
}
@Test
- public void onTargetsChanged_flingSpringAnimationsDisabled_floatingMenuIsCreated() {
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
- UserHandle.USER_CURRENT);
- final FakeFeatureFlags featureFlags = new FakeFeatureFlags();
- featureFlags.set(A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS, false);
-
- mController = setUpController();
- mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
-
- assertThat(mController.mFloatingMenu).isInstanceOf(AccessibilityFloatingMenu.class);
- }
-
- @Test
public void onTargetsChanged_isFloatingViewLayerControllerCreated() {
Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
UserHandle.USER_CURRENT);
- final FakeFeatureFlags featureFlags = new FakeFeatureFlags();
- featureFlags.set(A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS, true);
- mController = setUpController(featureFlags);
+ mController = setUpController();
mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
assertThat(mController.mFloatingMenu).isInstanceOf(MenuViewLayerController.class);
}
private AccessibilityFloatingMenuController setUpController() {
- final FakeFeatureFlags featureFlags = new FakeFeatureFlags();
- featureFlags.set(A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS, false);
- return setUpController(featureFlags);
- }
-
- private AccessibilityFloatingMenuController setUpController(FakeFeatureFlags featureFlags) {
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
final FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext);
@@ -361,7 +341,7 @@
final AccessibilityFloatingMenuController controller =
new AccessibilityFloatingMenuController(mContextWrapper, windowManager,
displayManager, mAccessibilityManager, mTargetsObserver, mModeObserver,
- mKeyguardUpdateMonitor, featureFlags, mSecureSettings, displayTracker);
+ mKeyguardUpdateMonitor, mSecureSettings, displayTracker);
controller.init();
return controller;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java
deleted file mode 100644
index 04345fd..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.doReturn;
-
-import android.content.Context;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.accessibility.AccessibilityManager;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.settings.SecureSettings;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/** Tests for {@link AccessibilityFloatingMenu}. */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class AccessibilityFloatingMenuTest extends SysuiTestCase {
-
- @Rule
- public MockitoRule mockito = MockitoJUnit.rule();
-
- @Mock
- private AccessibilityManager mAccessibilityManager;
- @Mock
- private SecureSettings mSecureSettings;
-
- private AccessibilityFloatingMenuView mMenuView;
- private AccessibilityFloatingMenu mMenu;
-
- @Before
- public void initMenu() {
- final List<String> assignedTargets = new ArrayList<>();
- mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
- assignedTargets.add(MAGNIFICATION_CONTROLLER_NAME);
- doReturn(assignedTargets).when(mAccessibilityManager).getAccessibilityShortcutTargets(
- anyInt());
-
- final Position position = new Position(0, 0);
- mMenuView = new AccessibilityFloatingMenuView(mContext, position);
- mMenu = new AccessibilityFloatingMenu(mContext, mSecureSettings, mMenuView);
- }
-
- @Test
- public void showMenuView_success() {
- mMenu.show();
-
- assertThat(mMenuView.isShowing()).isTrue();
- }
-
- @Test
- public void hideMenuView_success() {
- mMenu.show();
- mMenu.hide();
-
- assertThat(mMenuView.isShowing()).isFalse();
- }
-
- @Test
- public void showMenuView_emptyTarget_notShow() {
- final List<String> emptyTargets = new ArrayList<>();
- doReturn(emptyTargets).when(mAccessibilityManager).getAccessibilityShortcutTargets(
- anyInt());
-
- mMenu.show();
-
- assertThat(mMenuView.isShowing()).isFalse();
- }
-
- @After
- public void tearDown() {
- mMenu.hide();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
deleted file mode 100644
index bebd871..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
+++ /dev/null
@@ -1,527 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.view.View.OVER_SCROLL_ALWAYS;
-import static android.view.View.OVER_SCROLL_NEVER;
-import static android.view.WindowInsets.Type.displayCutout;
-import static android.view.WindowInsets.Type.ime;
-import static android.view.WindowInsets.Type.systemBars;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-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.content.Context;
-import android.content.res.Resources;
-import android.graphics.Insets;
-import android.graphics.Rect;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.LayerDrawable;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewPropertyAnimator;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.accessibility.dialog.AccessibilityTarget;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.accessibility.MotionEventHelper;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/** Tests for {@link AccessibilityFloatingMenuView}. */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
-
- @Rule
- public MockitoRule mockito = MockitoJUnit.rule();
-
- private final MotionEventHelper mMotionEventHelper = new MotionEventHelper();
- private final List<AccessibilityTarget> mTargets = new ArrayList<>(
- Collections.singletonList(mock(AccessibilityTarget.class)));
-
- private final Position mPlaceholderPosition = new Position(0.0f, 0.0f);
-
- @Mock
- private WindowManager mWindowManager;
- @Mock
- private ViewPropertyAnimator mAnimator;
- @Mock
- private WindowMetrics mWindowMetrics;
- private MotionEvent mInterceptMotionEvent;
- private AccessibilityFloatingMenuView mMenuView;
- private RecyclerView mListView = new RecyclerView(mContext);
-
- private int mMenuWindowHeight;
- private int mMenuHalfWidth;
- private int mMenuHalfHeight;
- private int mDisplayHalfWidth;
- private int mDisplayHalfHeight;
- private int mMaxWindowX;
- private int mMaxWindowY;
- private final int mDisplayWindowWidth = 1080;
- private final int mDisplayWindowHeight = 2340;
-
- @Before
- public void initMenuView() {
- final WindowManager wm = mContext.getSystemService(WindowManager.class);
- doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
- mWindowManager).getMaximumWindowMetrics();
- mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
- when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
- when(mWindowMetrics.getBounds()).thenReturn(new Rect(0, 0, mDisplayWindowWidth,
- mDisplayWindowHeight));
- when(mWindowMetrics.getWindowInsets()).thenReturn(fakeDisplayInsets());
- mMenuView = spy(
- new AccessibilityFloatingMenuView(mContext, mPlaceholderPosition, mListView));
- }
-
- @Before
- public void setUpMatrices() {
- final Resources res = mContext.getResources();
- final int margin =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_margin);
- final int padding =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_padding);
- final int iconWidthHeight =
- res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_width_height);
- final int menuWidth = padding * 2 + iconWidthHeight;
- final int menuHeight = (padding + iconWidthHeight) * mTargets.size() + padding;
- mMenuHalfWidth = menuWidth / 2;
- mMenuHalfHeight = menuHeight / 2;
- mDisplayHalfWidth = mDisplayWindowWidth / 2;
- mDisplayHalfHeight = mDisplayWindowHeight / 2;
- int marginStartEnd =
- mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT
- ? margin : 0;
- mMaxWindowX = mDisplayWindowWidth - marginStartEnd - menuWidth;
- mMenuWindowHeight = menuHeight + margin * 2;
- mMaxWindowY = mDisplayWindowHeight - mMenuWindowHeight;
- }
-
- @Test
- public void initListView_success() {
- assertThat(mListView.getCompatAccessibilityDelegate()).isNotNull();
- assertThat(mMenuView.getChildCount()).isEqualTo(1);
- }
-
- @Test
- public void showMenuView_success() {
- mMenuView.show();
-
- assertThat(mMenuView.isShowing()).isTrue();
- verify(mWindowManager).addView(eq(mMenuView), any(WindowManager.LayoutParams.class));
- }
-
- @Test
- public void showMenuView_showTwice_addViewOnce() {
- mMenuView.show();
- mMenuView.show();
-
- assertThat(mMenuView.isShowing()).isTrue();
- verify(mWindowManager, times(1)).addView(eq(mMenuView),
- any(WindowManager.LayoutParams.class));
- }
-
- @Test
- public void hideMenuView_success() {
- mMenuView.show();
- mMenuView.hide();
-
- assertThat(mMenuView.isShowing()).isFalse();
- verify(mWindowManager).removeView(eq(mMenuView));
- }
-
- @Test
- public void hideMenuView_hideTwice_removeViewOnce() {
- mMenuView.show();
- mMenuView.hide();
- mMenuView.hide();
-
- assertThat(mMenuView.isShowing()).isFalse();
- verify(mWindowManager, times(1)).removeView(eq(mMenuView));
- }
-
- @Test
- public void hideMenuViewWhenStartingAnimation_animatorNotRunning() {
- mMenuView.show();
-
- mMenuView.mDragAnimator.start();
- mMenuView.hide();
-
- assertThat(mMenuView.mDragAnimator.isRunning()).isFalse();
- }
-
- @Test
- public void onTargetsChanged_singleTarget_expectedRadii() {
- final Position alignRightPosition = new Position(1.0f, 0.0f);
- final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
- alignRightPosition);
- setupBasicMenuView(menuView);
-
- menuView.onTargetsChanged(mTargets);
-
- final View view = menuView.getChildAt(0);
- final LayerDrawable layerDrawable = (LayerDrawable) view.getBackground();
- final GradientDrawable gradientDrawable =
- (GradientDrawable) layerDrawable.getDrawable(0);
- final float smallRadius =
- getContext().getResources().getDimensionPixelSize(
- R.dimen.accessibility_floating_menu_small_single_radius);
- final float[] expectedRadii =
- new float[]{smallRadius, smallRadius, 0.0f, 0.0f, 0.0f, 0.0f, smallRadius,
- smallRadius};
- assertThat(gradientDrawable.getCornerRadii()).isEqualTo(expectedRadii);
- }
-
- @Test
- public void setSizeType_alignRightAndLargeSize_expectedRadii() {
- final RecyclerView listView = spy(new RecyclerView(mContext));
- final Position alignRightPosition = new Position(1.0f, 0.0f);
- final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
- alignRightPosition, listView);
- setupBasicMenuView(menuView);
-
- menuView.setSizeType(/* largeSize */ 1);
-
- final LayerDrawable layerDrawable =
- (LayerDrawable) listView.getBackground();
- final GradientDrawable gradientDrawable =
- (GradientDrawable) layerDrawable.getDrawable(0);
- final float largeRadius = getContext().getResources().getDimensionPixelSize(
- R.dimen.accessibility_floating_menu_large_single_radius);
- final float[] expectedRadii =
- new float[] {largeRadius, largeRadius, 0.0f, 0.0f, 0.0f, 0.0f, largeRadius,
- largeRadius};
- assertThat(gradientDrawable.getCornerRadii()).isEqualTo(expectedRadii);
- }
-
- @Test
- public void setShapeType_halfCircle_translationX() {
- final RecyclerView listView = spy(new RecyclerView(mContext));
- final AccessibilityFloatingMenuView menuView =
- new AccessibilityFloatingMenuView(mContext, mPlaceholderPosition, listView);
- setupBasicMenuView(menuView);
- doReturn(mAnimator).when(listView).animate();
-
- menuView.setShapeType(/* halfOvalShape */ 1);
-
- verify(mAnimator).translationX(anyFloat());
- }
-
- @Test
- public void onTargetsChanged_fadeInOut() {
- final InOrder inOrderMenuView = inOrder(mMenuView);
-
- mMenuView.onTargetsChanged(mTargets);
-
- inOrderMenuView.verify(mMenuView).fadeIn();
- inOrderMenuView.verify(mMenuView).fadeOut();
- }
-
- @Test
- public void setSizeType_fadeInOut() {
- final InOrder inOrderMenuView = inOrder(mMenuView);
-
- mMenuView.setSizeType(/* smallSize */ 0);
-
- inOrderMenuView.verify(mMenuView).fadeIn();
- inOrderMenuView.verify(mMenuView).fadeOut();
- }
-
- @Test
- public void tapOnAndDragMenu_interceptUpEvent() {
- final RecyclerView listView = new RecyclerView(mContext);
- final TestAccessibilityFloatingMenu menuView =
- new TestAccessibilityFloatingMenu(mContext, mPlaceholderPosition, listView);
- setupBasicMenuView(menuView);
- final int currentWindowX = menuView.mCurrentLayoutParams.x;
- final int currentWindowY = menuView.mCurrentLayoutParams.y;
- final MotionEvent downEvent =
- mMotionEventHelper.obtainMotionEvent(0, 1,
- MotionEvent.ACTION_DOWN,
- currentWindowX + /* offsetXToMenuCenterX */ mMenuHalfWidth,
- currentWindowY + /* offsetYToMenuCenterY */ mMenuHalfHeight);
- final MotionEvent moveEvent =
- mMotionEventHelper.obtainMotionEvent(2, 3,
- MotionEvent.ACTION_MOVE,
- /* displayCenterX */mDisplayHalfWidth
- - /* offsetXToDisplayLeftHalfRegion */ 10,
- /* displayCenterY */ mDisplayHalfHeight);
- final MotionEvent upEvent =
- mMotionEventHelper.obtainMotionEvent(4, 5,
- MotionEvent.ACTION_UP,
- /* displayCenterX */ mDisplayHalfWidth
- - /* offsetXToDisplayLeftHalfRegion */ 10,
- /* displayCenterY */ mDisplayHalfHeight);
-
- listView.dispatchTouchEvent(downEvent);
- listView.dispatchTouchEvent(moveEvent);
- listView.dispatchTouchEvent(upEvent);
-
- assertThat(mInterceptMotionEvent.getAction()).isEqualTo(MotionEvent.ACTION_UP);
- }
-
- @Test
- public void tapOnAndDragMenu_matchLocation() {
- final float expectedX = 1.0f;
- final float expectedY = 0.7f;
- final Position position = new Position(expectedX, expectedY);
- final RecyclerView listView = new RecyclerView(mContext);
- final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
- position, listView);
- setupBasicMenuView(menuView);
- final int currentWindowX = menuView.mCurrentLayoutParams.x;
- final int currentWindowY = menuView.mCurrentLayoutParams.y;
- final MotionEvent downEvent =
- mMotionEventHelper.obtainMotionEvent(0, 1,
- MotionEvent.ACTION_DOWN,
- currentWindowX + /* offsetXToMenuCenterX */ mMenuHalfWidth,
- currentWindowY + /* offsetYToMenuCenterY */ mMenuHalfHeight);
- final MotionEvent moveEvent =
- mMotionEventHelper.obtainMotionEvent(2, 3,
- MotionEvent.ACTION_MOVE,
- /* displayCenterX */mDisplayHalfWidth
- + /* offsetXToDisplayRightHalfRegion */ 10,
- /* displayCenterY */ mDisplayHalfHeight);
- final MotionEvent upEvent =
- mMotionEventHelper.obtainMotionEvent(4, 5,
- MotionEvent.ACTION_UP,
- /* displayCenterX */ mDisplayHalfWidth
- + /* offsetXToDisplayRightHalfRegion */ 10,
- /* displayCenterY */ mDisplayHalfHeight);
-
- listView.dispatchTouchEvent(downEvent);
- listView.dispatchTouchEvent(moveEvent);
- listView.dispatchTouchEvent(upEvent);
- menuView.mDragAnimator.end();
-
- assertThat((float) menuView.mCurrentLayoutParams.x).isWithin(1.0f).of(mMaxWindowX);
- assertThat((float) menuView.mCurrentLayoutParams.y).isWithin(1.0f).of(
- /* newWindowY = displayCenterY - offsetY */ mDisplayHalfHeight - mMenuHalfHeight);
- }
-
-
- @Test
- public void tapOnAndDragMenuToDisplaySide_transformShapeHalfOval() {
- final Position alignRightPosition = new Position(1.0f, 0.8f);
- final RecyclerView listView = new RecyclerView(mContext);
- final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
- alignRightPosition, listView);
- setupBasicMenuView(menuView);
-
- final int currentWindowX = menuView.mCurrentLayoutParams.x;
- final int currentWindowY = menuView.mCurrentLayoutParams.y;
- final MotionEvent downEvent =
- mMotionEventHelper.obtainMotionEvent(0, 1,
- MotionEvent.ACTION_DOWN,
- currentWindowX + /* offsetXToMenuCenterX */ mMenuHalfWidth,
- currentWindowY + /* offsetYToMenuCenterY */ mMenuHalfHeight);
- final MotionEvent moveEvent =
- mMotionEventHelper.obtainMotionEvent(2, 3,
- MotionEvent.ACTION_MOVE,
- /* downX */(currentWindowX + mMenuHalfWidth)
- + /* offsetXToDisplayRightSide */ mMenuHalfWidth,
- /* downY */ (currentWindowY + mMenuHalfHeight));
- final MotionEvent upEvent =
- mMotionEventHelper.obtainMotionEvent(4, 5,
- MotionEvent.ACTION_UP,
- /* downX */(currentWindowX + mMenuHalfWidth)
- + /* offsetXToDisplayRightSide */ mMenuHalfWidth,
- /* downY */ (currentWindowY + mMenuHalfHeight));
-
- listView.dispatchTouchEvent(downEvent);
- listView.dispatchTouchEvent(moveEvent);
- listView.dispatchTouchEvent(upEvent);
-
- assertThat(menuView.mShapeType).isEqualTo(/* halfOval */ 1);
- }
-
- @Test
- public void onTargetsChanged_exceedAvailableHeight_overScrollAlways() {
- doReturn(true).when(mMenuView).hasExceededMaxLayoutHeight();
-
- mMenuView.onTargetsChanged(mTargets);
-
- assertThat(mListView.getOverScrollMode()).isEqualTo(OVER_SCROLL_ALWAYS);
- }
-
- @Test
- public void onTargetsChanged_notExceedAvailableHeight_overScrollNever() {
- doReturn(false).when(mMenuView).hasExceededMaxLayoutHeight();
-
- mMenuView.onTargetsChanged(mTargets);
-
- assertThat(mListView.getOverScrollMode()).isEqualTo(OVER_SCROLL_NEVER);
- }
-
- @Test
- public void showMenuView_insetsListener_overlapWithIme_menuViewShifted() {
- final int offset = 200;
-
- final Position alignRightPosition = new Position(1.0f, 0.8f);
- final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
- alignRightPosition);
- setupBasicMenuView(menuView);
- final WindowInsets imeInset = fakeImeInsetWith(menuView, offset);
- when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
- when(mWindowMetrics.getWindowInsets()).thenReturn(imeInset);
- final int expectedLayoutY = menuView.mCurrentLayoutParams.y - offset;
- menuView.dispatchApplyWindowInsets(imeInset);
-
- assertThat(menuView.mCurrentLayoutParams.y).isEqualTo(expectedLayoutY);
- }
-
- @Test
- public void hideIme_onMenuViewShifted_menuViewMovedBack() {
- final int offset = 200;
- setupBasicMenuView(mMenuView);
- final WindowInsets imeInset = fakeImeInsetWith(mMenuView, offset);
- when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
- when(mWindowMetrics.getWindowInsets()).thenReturn(imeInset);
- final int expectedLayoutY = mMenuView.mCurrentLayoutParams.y;
- mMenuView.dispatchApplyWindowInsets(imeInset);
-
- mMenuView.dispatchApplyWindowInsets(
- new WindowInsets.Builder().setVisible(ime(), false).build());
-
- assertThat(mMenuView.mCurrentLayoutParams.y).isEqualTo(expectedLayoutY);
- }
-
- @Test
- public void showMenuAndIme_withHigherIme_alignDisplayTopEdge() {
- final int offset = 99999;
-
- setupBasicMenuView(mMenuView);
- final WindowInsets imeInset = fakeImeInsetWith(mMenuView, offset);
- when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
- when(mWindowMetrics.getWindowInsets()).thenReturn(imeInset);
- mMenuView.dispatchApplyWindowInsets(imeInset);
-
- assertThat(mMenuView.mCurrentLayoutParams.y).isEqualTo(0);
- }
-
- @Test
- public void testConstructor_withPosition_expectedPosition() {
- final float expectedX = 1.0f;
- final float expectedY = 0.7f;
- final Position position = new Position(expectedX, expectedY);
-
- final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
- position);
- setupBasicMenuView(menuView);
-
- assertThat((float) menuView.mCurrentLayoutParams.x).isWithin(1.0f).of(mMaxWindowX);
- assertThat((float) menuView.mCurrentLayoutParams.y).isWithin(1.0f).of(
- expectedY * mMaxWindowY);
- }
-
- @After
- public void tearDown() {
- mInterceptMotionEvent = null;
- mMotionEventHelper.recycleEvents();
- mListView = null;
- }
-
- private void setupBasicMenuView(AccessibilityFloatingMenuView menuView) {
- menuView.show();
- menuView.onTargetsChanged(mTargets);
- menuView.setSizeType(0);
- menuView.setShapeType(0);
- }
-
- /**
- * Based on the current menu status, fake the ime inset component {@link WindowInsets} used
- * for testing.
- *
- * @param menuView {@link AccessibilityFloatingMenuView} that needs to be changed
- * @param offset is used for the y-axis position of ime higher than the y-axis position of menu
- * @return the ime inset
- */
- private WindowInsets fakeImeInsetWith(AccessibilityFloatingMenuView menuView, int offset) {
- // Ensure the keyboard has overlapped on the menu view.
- final int fakeImeHeight =
- mDisplayWindowHeight - (menuView.mCurrentLayoutParams.y + mMenuWindowHeight)
- + offset;
- return new WindowInsets.Builder()
- .setVisible(ime(), true)
- .setInsets(ime(), Insets.of(0, 0, 0, fakeImeHeight))
- .build();
- }
-
- private WindowInsets fakeDisplayInsets() {
- final int fakeStatusBarHeight = 75;
- final int fakeNavigationBarHeight = 125;
- return new WindowInsets.Builder()
- .setVisible(systemBars() | displayCutout(), true)
- .setInsets(systemBars() | displayCutout(),
- Insets.of(0, fakeStatusBarHeight, 0, fakeNavigationBarHeight))
- .build();
- }
-
- private class TestAccessibilityFloatingMenu extends AccessibilityFloatingMenuView {
- TestAccessibilityFloatingMenu(Context context, Position position, RecyclerView listView) {
- super(context, position, listView);
- }
-
- @Override
- public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,
- @NonNull MotionEvent event) {
- final boolean intercept = super.onInterceptTouchEvent(recyclerView, event);
-
- if (intercept) {
- mInterceptMotionEvent = event;
- }
-
- return intercept;
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipViewTest.java
deleted file mode 100644
index 3553a0a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipViewTest.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.MotionEvent;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-import android.view.accessibility.AccessibilityNodeInfo;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.accessibility.MotionEventHelper;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/** Tests for {@link BaseTooltipView}. */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class BaseTooltipViewTest extends SysuiTestCase {
-
- @Mock
- private WindowManager mWindowManager;
-
- @Mock
- private WindowMetrics mWindowMetrics;
-
- private AccessibilityFloatingMenuView mMenuView;
- private BaseTooltipView mToolTipView;
-
- private final Position mPlaceholderPosition = new Position(0.0f, 0.0f);
- private final MotionEventHelper mMotionEventHelper = new MotionEventHelper();
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- final WindowManager wm = mContext.getSystemService(WindowManager.class);
- doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
- mWindowManager).getMaximumWindowMetrics();
- mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
- when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
- when(mWindowMetrics.getBounds()).thenReturn(new Rect());
- when(mWindowMetrics.getWindowInsets()).thenReturn(new WindowInsets.Builder().build());
-
- mMenuView = new AccessibilityFloatingMenuView(mContext, mPlaceholderPosition);
- mToolTipView = new BaseTooltipView(mContext, mMenuView);
- }
-
- @Test
- public void showToolTipView_success() {
- mToolTipView.show();
-
- verify(mWindowManager).addView(eq(mToolTipView), any(WindowManager.LayoutParams.class));
- }
-
- @Test
- public void touchOutsideWhenToolTipViewShown_dismiss() {
- final MotionEvent outsideEvent =
- mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0,
- /* eventTime= */1,
- MotionEvent.ACTION_OUTSIDE,
- /* x= */ 0,
- /* y= */ 0);
-
- mToolTipView.show();
- mToolTipView.dispatchTouchEvent(outsideEvent);
-
- verify(mWindowManager).removeView(mToolTipView);
- }
-
- @Test
- public void getAccessibilityActionList_matchResult() {
- final AccessibilityNodeInfo infos = new AccessibilityNodeInfo();
- mToolTipView.onInitializeAccessibilityNodeInfo(infos);
-
- assertThat(infos.getActionList().size()).isEqualTo(1);
- }
-
- @Test
- public void accessibilityAction_dismiss_success() {
- final BaseTooltipView tooltipView =
- spy(new BaseTooltipView(mContext, mMenuView));
-
- final boolean isActionPerformed =
- tooltipView.performAccessibilityAction(
- AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS.getId(),
- /* arguments= */ null);
-
- assertThat(isActionPerformed).isTrue();
- verify(tooltipView).hide();
- }
-
- @After
- public void tearDown() {
- mToolTipView.hide();
- mMotionEventHelper.recycleEvents();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DockTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DockTooltipViewTest.java
deleted file mode 100644
index 9eba49d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DockTooltipViewTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.MotionEvent;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.accessibility.MotionEventHelper;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/** Tests for {@link DockTooltipView}. */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class DockTooltipViewTest extends SysuiTestCase {
-
- @Mock
- private WindowManager mWindowManager;
-
- @Mock
- private WindowMetrics mWindowMetrics;
-
- private AccessibilityFloatingMenuView mMenuView;
- private DockTooltipView mDockTooltipView;
- private final Position mPlaceholderPosition = new Position(0.0f, 0.0f);
- private final MotionEventHelper mMotionEventHelper = new MotionEventHelper();
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- final WindowManager wm = mContext.getSystemService(WindowManager.class);
- doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
- mWindowManager).getMaximumWindowMetrics();
- mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
- when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
- when(mWindowMetrics.getBounds()).thenReturn(new Rect());
- when(mWindowMetrics.getWindowInsets()).thenReturn(new WindowInsets.Builder().build());
-
- mMenuView = spy(new AccessibilityFloatingMenuView(mContext, mPlaceholderPosition));
- mDockTooltipView = new DockTooltipView(mContext, mMenuView);
- }
-
- @Test
- public void showTooltip_success() {
- mDockTooltipView.show();
-
- verify(mMenuView).startTranslateXAnimation();
- verify(mWindowManager).addView(eq(mDockTooltipView), any(WindowManager.LayoutParams.class));
- }
-
- @Test
- public void hideTooltip_success() {
- mDockTooltipView.show();
- mDockTooltipView.hide();
-
- verify(mMenuView).stopTranslateXAnimation();
- verify(mWindowManager).removeView(mDockTooltipView);
- }
-
- @Test
- public void touchOutsideWhenToolTipViewShown_stopAnimation() {
- final MotionEvent outsideEvent =
- mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0,
- /* eventTime= */ 1,
- MotionEvent.ACTION_OUTSIDE,
- /* x= */ 0,
- /* y= */ 0);
-
- mDockTooltipView.show();
- mDockTooltipView.dispatchTouchEvent(outsideEvent);
-
- verify(mMenuView).stopTranslateXAnimation();
- }
-
- @After
- public void tearDown() {
- mMotionEventHelper.recycleEvents();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompatTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompatTest.java
deleted file mode 100644
index ea104a7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompatTest.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-import android.view.accessibility.AccessibilityNodeInfo;
-
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-/** Tests for {@link ItemDelegateCompat}. */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class ItemDelegateCompatTest extends SysuiTestCase {
- @Rule
- public MockitoRule mockito = MockitoJUnit.rule();
-
- @Mock
- private WindowManager mWindowManager;
-
- @Mock
- private WindowMetrics mWindowMetrics;
- private RecyclerView mListView;
- private AccessibilityFloatingMenuView mMenuView;
- private ItemDelegateCompat mItemDelegateCompat;
- private final Rect mAvailableBounds = new Rect(100, 200, 300, 400);
- private final Position mPlaceholderPosition = new Position(0.0f, 0.0f);
-
- @Before
- public void setUp() {
- final WindowManager wm = mContext.getSystemService(WindowManager.class);
- doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
- mWindowManager).getMaximumWindowMetrics();
- mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
- when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
- when(mWindowMetrics.getBounds()).thenReturn(new Rect());
- when(mWindowMetrics.getWindowInsets()).thenReturn(new WindowInsets.Builder().build());
-
- mListView = new RecyclerView(mContext);
- mMenuView =
- spy(new AccessibilityFloatingMenuView(mContext, mPlaceholderPosition, mListView));
- mItemDelegateCompat =
- new ItemDelegateCompat(new RecyclerViewAccessibilityDelegate(mListView), mMenuView);
- }
-
- @Test
- public void getAccessibilityActionList_matchResult() {
- final AccessibilityNodeInfoCompat info =
- new AccessibilityNodeInfoCompat(new AccessibilityNodeInfo());
-
- mItemDelegateCompat.onInitializeAccessibilityNodeInfo(mListView, info);
-
- assertThat(info.getActionList().size()).isEqualTo(5);
- }
-
- @Test
- public void performAccessibilityMoveTopLeftAction_halfOval_success() {
- doReturn(mAvailableBounds).when(mMenuView).getAvailableBounds();
- mMenuView.setShapeType(/* halfOvalShape */ 1);
-
- final boolean moveTopLeftAction =
- mItemDelegateCompat.performAccessibilityAction(mListView, R.id.action_move_top_left,
- null);
-
- assertThat(moveTopLeftAction).isTrue();
- assertThat(mMenuView.mShapeType).isEqualTo(/* ovalShape */ 0);
- verify(mMenuView).snapToLocation(mAvailableBounds.left, mAvailableBounds.top);
- }
-
- @Test
- public void performAccessibilityMoveTopRightAction_halfOval_success() {
- doReturn(mAvailableBounds).when(mMenuView).getAvailableBounds();
- mMenuView.setShapeType(/* halfOvalShape */ 1);
-
- final boolean moveTopRightAction =
- mItemDelegateCompat.performAccessibilityAction(mListView,
- R.id.action_move_top_right, null);
-
- assertThat(moveTopRightAction).isTrue();
- assertThat(mMenuView.mShapeType).isEqualTo(/* ovalShape */ 0);
- verify(mMenuView).snapToLocation(mAvailableBounds.right, mAvailableBounds.top);
- }
-
- @Test
- public void performAccessibilityMoveBottomLeftAction_halfOval_success() {
- doReturn(mAvailableBounds).when(mMenuView).getAvailableBounds();
- mMenuView.setShapeType(/* halfOvalShape */ 1);
-
- final boolean moveBottomLeftAction =
- mItemDelegateCompat.performAccessibilityAction(mListView,
- R.id.action_move_bottom_left, null);
-
- assertThat(moveBottomLeftAction).isTrue();
- assertThat(mMenuView.mShapeType).isEqualTo(/* ovalShape */ 0);
- verify(mMenuView).snapToLocation(mAvailableBounds.left, mAvailableBounds.bottom);
- }
-
- @Test
- public void performAccessibilityMoveBottomRightAction_halfOval_success() {
- doReturn(mAvailableBounds).when(mMenuView).getAvailableBounds();
- mMenuView.setShapeType(/* halfOvalShape */ 1);
-
- final boolean moveBottomRightAction =
- mItemDelegateCompat.performAccessibilityAction(mListView,
- R.id.action_move_bottom_right, null);
-
- assertThat(moveBottomRightAction).isTrue();
- assertThat(mMenuView.mShapeType).isEqualTo(/* ovalShape */ 0);
- verify(mMenuView).snapToLocation(mAvailableBounds.right, mAvailableBounds.bottom);
- }
-
- @Test
- public void performAccessibilityMoveOutEdgeAction_halfOval_success() {
- doReturn(mAvailableBounds).when(mMenuView).getAvailableBounds();
- mMenuView.setShapeType(/* halfOvalShape */ 1);
-
- final boolean moveOutEdgeAndShowAction =
- mItemDelegateCompat.performAccessibilityAction(mListView,
- R.id.action_move_out_edge_and_show, null);
-
- assertThat(moveOutEdgeAndShowAction).isTrue();
- assertThat(mMenuView.mShapeType).isEqualTo(/* ovalShape */ 0);
- }
-
- @Test
- public void setupAccessibilityActions_oval_hasActionMoveToEdgeAndHide() {
- final AccessibilityNodeInfoCompat info =
- new AccessibilityNodeInfoCompat(new AccessibilityNodeInfo());
- mMenuView.setShapeType(/* ovalShape */ 0);
-
- mItemDelegateCompat.onInitializeAccessibilityNodeInfo(mListView, info);
-
- assertThat(info.getActionList().stream().anyMatch(
- action -> action.getId() == R.id.action_move_to_edge_and_hide)).isTrue();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MigrationTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MigrationTooltipViewTest.java
deleted file mode 100644
index 2fb0a90..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MigrationTooltipViewTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility.floatingmenu;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.text.SpannableString;
-import android.text.method.LinkMovementMethod;
-import android.widget.TextView;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Tests for {@link MigrationTooltipView}. */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class MigrationTooltipViewTest extends SysuiTestCase {
-
- private TextView mTextView;
- private final Position mPlaceholderPosition = new Position(0.0f, 0.0f);
-
- @Before
- public void setUp() {
- final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
- mPlaceholderPosition);
- final MigrationTooltipView toolTipView = new MigrationTooltipView(mContext, menuView);
- mTextView = toolTipView.findViewById(R.id.text);
- }
-
- @Test
- public void onCreate_setLinkMovementMethod() {
- assertThat(mTextView.getMovementMethod()).isInstanceOf(LinkMovementMethod.class);
- }
-
- @Test
- public void onCreate_setDescription_matchTextAndSpanNum() {
- final CharSequence expectedTextWithoutSpan =
- AnnotationLinkSpan.linkify(mContext.getText(
- R.string.accessibility_floating_button_migration_tooltip)).toString();
- final SpannableString spannableString = (SpannableString) mTextView.getText();
- final int AnnotationLinkSpanNum =
- spannableString.getSpans(/* queryStart= */ 0, spannableString.length(),
- AnnotationLinkSpan.class).length;
-
- assertThat(AnnotationLinkSpanNum).isEqualTo(1);
- assertThat(mTextView.getText().toString().contentEquals(expectedTextWithoutSpan)).isTrue();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index fd931b0..41beada 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -52,18 +52,16 @@
import androidx.test.filters.SmallTest
import com.airbnb.lottie.LottieAnimationView
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.ViewMediatorCallback
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestableContext
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags.MODERN_ALTERNATE_BOUNCER
-import com.android.systemui.keyguard.data.repository.FakeBiometricRepository
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
@@ -111,7 +109,7 @@
@Captor lateinit var overlayCaptor: ArgumentCaptor<View>
@Captor lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
- private lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
+ private lateinit var keyguardBouncerRepository: FakeKeyguardBouncerRepository
private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
private val featureFlags = FakeFeatureFlags()
private val executor = FakeExecutor(FakeSystemClock())
@@ -135,17 +133,11 @@
@Before
fun setup() {
featureFlags.set(MODERN_ALTERNATE_BOUNCER, true)
- keyguardBouncerRepository =
- KeyguardBouncerRepository(
- mock(ViewMediatorCallback::class.java),
- FakeSystemClock(),
- TestCoroutineScope(),
- mock(TableLogBuffer::class.java),
- )
+ keyguardBouncerRepository = FakeKeyguardBouncerRepository()
alternateBouncerInteractor =
AlternateBouncerInteractor(
keyguardBouncerRepository,
- FakeBiometricRepository(),
+ FakeBiometricSettingsRepository(),
FakeDeviceEntryFingerprintAuthRepository(),
FakeSystemClock(),
mock(KeyguardUpdateMonitor::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index 81a6bc2..c73ff1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -26,9 +26,10 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
-import com.android.systemui.keyguard.data.repository.BiometricRepository
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
@@ -65,7 +66,7 @@
allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread
MockitoAnnotations.initMocks(this)
keyguardBouncerRepository =
- KeyguardBouncerRepository(
+ KeyguardBouncerRepositoryImpl(
mock(com.android.keyguard.ViewMediatorCallback::class.java),
FakeSystemClock(),
TestCoroutineScope(),
@@ -91,7 +92,7 @@
mAlternateBouncerInteractor =
AlternateBouncerInteractor(
keyguardBouncerRepository,
- mock(BiometricRepository::class.java),
+ mock(BiometricSettingsRepository::class.java),
mock(DeviceEntryFingerprintAuthRepository::class.java),
mock(SystemClock::class.java),
mock(KeyguardUpdateMonitor::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
index af46d9b..4b41537 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt
@@ -16,30 +16,23 @@
package com.android.systemui.biometrics.udfps
-import android.graphics.Point
import android.graphics.Rect
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.EllipseOverlapDetectorParams
import com.google.common.truth.Truth.assertThat
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.`when` as whenEver
@SmallTest
@RunWith(Parameterized::class)
class EllipseOverlapDetectorTest(val testCase: TestCase) : SysuiTestCase() {
- val underTest = spy(EllipseOverlapDetector(neededPoints = 1))
-
- @Before
- fun setUp() {
- // Use one single center point for testing, required or total number of points may change
- whenEver(underTest.calculateSensorPoints(SENSOR))
- .thenReturn(listOf(Point(SENSOR.centerX(), SENSOR.centerY())))
- }
+ val underTest =
+ EllipseOverlapDetector(
+ EllipseOverlapDetectorParams(minOverlap = .4f, targetSize = .2f, stepSize = 1)
+ )
@Test
fun isGoodOverlap() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index 71c335e..7177919 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.clipboardoverlay;
+import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
+
import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
import static org.junit.Assert.assertEquals;
@@ -38,6 +40,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlags;
import org.junit.Before;
import org.junit.Test;
@@ -60,6 +63,7 @@
private ClipboardOverlayController mOverlayController;
@Mock
private ClipboardToast mClipboardToast;
+ private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@Mock
private UiEventLogger mUiEventLogger;
@@ -93,8 +97,10 @@
when(mClipboardManager.getPrimaryClip()).thenReturn(mSampleClipData);
when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, true);
+
mClipboardListener = new ClipboardListener(getContext(), mOverlayControllerProvider,
- mClipboardToast, mClipboardManager, mUiEventLogger);
+ mClipboardToast, mClipboardManager, mFeatureFlags, mUiEventLogger);
}
@@ -187,4 +193,34 @@
verify(mClipboardToast, times(1)).showCopiedToast();
verifyZeroInteractions(mOverlayControllerProvider);
}
+
+ @Test
+ public void test_minimizedLayoutFlagOff_usesLegacy() {
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
+
+ mClipboardListener.start();
+ mClipboardListener.onPrimaryClipChanged();
+
+ verify(mOverlayControllerProvider).get();
+
+ verify(mOverlayController).setClipDataLegacy(
+ mClipDataCaptor.capture(), mStringCaptor.capture());
+
+ assertEquals(mSampleClipData, mClipDataCaptor.getValue());
+ assertEquals(mSampleSource, mStringCaptor.getValue());
+ }
+
+ @Test
+ public void test_minimizedLayoutFlagOn_usesNew() {
+ mClipboardListener.start();
+ mClipboardListener.onPrimaryClipChanged();
+
+ verify(mOverlayControllerProvider).get();
+
+ verify(mOverlayController).setClipData(
+ mClipDataCaptor.capture(), mStringCaptor.capture());
+
+ assertEquals(mSampleClipData, mClipDataCaptor.getValue());
+ assertEquals(mSampleSource, mStringCaptor.getValue());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
new file mode 100644
index 0000000..faef35e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.clipboardoverlay
+
+import android.content.ClipData
+import android.content.ClipDescription
+import android.content.ContentResolver
+import android.content.Context
+import android.graphics.Bitmap
+import android.net.Uri
+import android.os.PersistableBundle
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.whenever
+import java.io.IOException
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ClipboardModelTest : SysuiTestCase() {
+ @Mock private lateinit var mClipboardUtils: ClipboardOverlayUtils
+ @Mock private lateinit var mMockContext: Context
+ @Mock private lateinit var mMockContentResolver: ContentResolver
+ private lateinit var mSampleClipData: ClipData
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ mSampleClipData = ClipData("Test", arrayOf("text/plain"), ClipData.Item("Test Item"))
+ }
+
+ @Test
+ fun test_nullClipData() {
+ val model = ClipboardModel.fromClipData(mContext, mClipboardUtils, null, "test source")
+ assertNull(model.clipData)
+ assertEquals("test source", model.source)
+ assertEquals(ClipboardModel.Type.OTHER, model.type)
+ assertNull(model.item)
+ assertFalse(model.isSensitive)
+ assertFalse(model.isRemote)
+ assertNull(model.loadThumbnail(mContext))
+ }
+
+ @Test
+ fun test_textClipData() {
+ val source = "test source"
+ val model = ClipboardModel.fromClipData(mContext, mClipboardUtils, mSampleClipData, source)
+ assertEquals(mSampleClipData, model.clipData)
+ assertEquals(source, model.source)
+ assertEquals(ClipboardModel.Type.TEXT, model.type)
+ assertEquals(mSampleClipData.getItemAt(0), model.item)
+ assertFalse(model.isSensitive)
+ assertFalse(model.isRemote)
+ assertNull(model.loadThumbnail(mContext))
+ }
+
+ @Test
+ fun test_sensitiveExtra() {
+ val description = mSampleClipData.description
+ val b = PersistableBundle()
+ b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
+ description.extras = b
+ val data = ClipData(description, mSampleClipData.getItemAt(0))
+ val (_, _, _, _, sensitive) =
+ ClipboardModel.fromClipData(mContext, mClipboardUtils, data, "")
+ assertTrue(sensitive)
+ }
+
+ @Test
+ fun test_remoteExtra() {
+ whenever(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true)
+ val model = ClipboardModel.fromClipData(mContext, mClipboardUtils, mSampleClipData, "")
+ assertTrue(model.isRemote)
+ }
+
+ @Test
+ @Throws(IOException::class)
+ fun test_imageClipData() {
+ val testBitmap = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888)
+ whenever(mMockContext.contentResolver).thenReturn(mMockContentResolver)
+ whenever(mMockContext.resources).thenReturn(mContext.resources)
+ whenever(mMockContentResolver.loadThumbnail(any(), any(), any())).thenReturn(testBitmap)
+ whenever(mMockContentResolver.getType(any())).thenReturn("image")
+ val imageClipData =
+ ClipData("Test", arrayOf("text/plain"), ClipData.Item(Uri.parse("test")))
+ val model = ClipboardModel.fromClipData(mMockContext, mClipboardUtils, imageClipData, "")
+ assertEquals(ClipboardModel.Type.IMAGE, model.type)
+ assertEquals(testBitmap, model.loadThumbnail(mMockContext))
+ }
+
+ @Test
+ @Throws(IOException::class)
+ fun test_imageClipData_loadFailure() {
+ whenever(mMockContext.contentResolver).thenReturn(mMockContentResolver)
+ whenever(mMockContext.resources).thenReturn(mContext.resources)
+ whenever(mMockContentResolver.loadThumbnail(any(), any(), any())).thenThrow(IOException())
+ whenever(mMockContentResolver.getType(any())).thenReturn("image")
+ val imageClipData =
+ ClipData("Test", arrayOf("text/plain"), ClipData.Item(Uri.parse("test")))
+ val model = ClipboardModel.fromClipData(mMockContext, mClipboardUtils, imageClipData, "")
+ assertEquals(ClipboardModel.Type.IMAGE, model.type)
+ assertNull(model.loadThumbnail(mMockContext))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index ca5b7af..0ac2667 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -16,13 +16,17 @@
package com.android.systemui.clipboardoverlay;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
+import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
import static com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -35,8 +39,11 @@
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Rect;
import android.net.Uri;
import android.os.PersistableBundle;
+import android.view.WindowInsets;
import android.view.textclassifier.TextLinks;
import androidx.test.filters.SmallTest;
@@ -102,11 +109,14 @@
when(mClipboardOverlayView.getEnterAnimation()).thenReturn(mAnimator);
when(mClipboardOverlayView.getExitAnimation()).thenReturn(mAnimator);
+ when(mClipboardOverlayWindow.getWindowInsets()).thenReturn(
+ getImeInsets(new Rect(0, 0, 0, 0)));
mSampleClipData = new ClipData("Test", new String[]{"text/plain"},
new ClipData.Item("Test Item"));
mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, false);
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, true); // turned off for legacy tests
mOverlayController = new ClipboardOverlayController(
mContext,
@@ -118,8 +128,7 @@
mFeatureFlags,
mClipboardUtils,
mExecutor,
- mUiEventLogger,
- mDisplayTracker);
+ mUiEventLogger);
verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
mCallbacks = mOverlayCallbacksCaptor.getValue();
}
@@ -130,6 +139,159 @@
}
@Test
+ public void test_setClipData_nullData_legacy() {
+ ClipData clipData = null;
+ mOverlayController.setClipDataLegacy(clipData, "");
+
+ verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+ verify(mClipboardOverlayView, times(0)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_invalidImageData_legacy() {
+ ClipData clipData = new ClipData("", new String[]{"image/png"},
+ new ClipData.Item(Uri.parse("")));
+
+ mOverlayController.setClipDataLegacy(clipData, "");
+
+ verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+ verify(mClipboardOverlayView, times(0)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_textData_legacy() {
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+ verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false);
+ verify(mClipboardOverlayView, times(1)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_sensitiveTextData_legacy() {
+ ClipDescription description = mSampleClipData.getDescription();
+ PersistableBundle b = new PersistableBundle();
+ b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
+ description.setExtras(b);
+ ClipData data = new ClipData(description, mSampleClipData.getItemAt(0));
+ mOverlayController.setClipDataLegacy(data, "");
+
+ verify(mClipboardOverlayView, times(1)).showTextPreview("••••••", true);
+ verify(mClipboardOverlayView, times(1)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_repeatedCalls_legacy() {
+ when(mAnimator.isRunning()).thenReturn(true);
+
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_viewCallbacks_onShareTapped_legacy() {
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+ mCallbacks.onShareButtonTapped();
+
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "");
+ verify(mClipboardOverlayView, times(1)).getExitAnimation();
+ }
+
+ @Test
+ public void test_viewCallbacks_onDismissTapped_legacy() {
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+ mCallbacks.onDismissButtonTapped();
+
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "");
+ verify(mClipboardOverlayView, times(1)).getExitAnimation();
+ }
+
+ @Test
+ public void test_multipleDismissals_dismissesOnce_legacy() {
+ mCallbacks.onSwipeDismissInitiated(mAnimator);
+ mCallbacks.onDismissButtonTapped();
+ mCallbacks.onSwipeDismissInitiated(mAnimator);
+ mCallbacks.onDismissButtonTapped();
+
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED, 0, null);
+ verify(mUiEventLogger, never()).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+ }
+
+ @Test
+ public void test_remoteCopy_withFlagOn_legacy() {
+ mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
+ when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
+
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+ verify(mTimeoutHandler, never()).resetTimeout();
+ }
+
+ @Test
+ public void test_remoteCopy_withFlagOff_legacy() {
+ when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
+
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+ verify(mTimeoutHandler).resetTimeout();
+ }
+
+ @Test
+ public void test_nonRemoteCopy_legacy() {
+ mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
+ when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(false);
+
+ mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+ verify(mTimeoutHandler).resetTimeout();
+ }
+
+ @Test
+ public void test_logsUseLastClipSource_legacy() {
+ mOverlayController.setClipDataLegacy(mSampleClipData, "first.package");
+ mCallbacks.onDismissButtonTapped();
+ mOverlayController.setClipDataLegacy(mSampleClipData, "second.package");
+ mCallbacks.onDismissButtonTapped();
+
+ verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "first.package");
+ verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "second.package");
+ verifyNoMoreInteractions(mUiEventLogger);
+ }
+
+ @Test
+ public void test_logOnClipboardActionsShown_legacy() {
+ ClipData.Item item = mSampleClipData.getItemAt(0);
+ item.setTextLinks(Mockito.mock(TextLinks.class));
+ mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
+ when(mClipboardUtils.isRemoteCopy(any(Context.class), any(ClipData.class), anyString()))
+ .thenReturn(true);
+ when(mClipboardUtils.getAction(any(ClipData.Item.class), anyString()))
+ .thenReturn(Optional.of(Mockito.mock(RemoteAction.class)));
+ when(mClipboardOverlayView.post(any(Runnable.class))).thenAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ }
+ });
+
+ mOverlayController.setClipDataLegacy(
+ new ClipData(mSampleClipData.getDescription(), item), "actionShownSource");
+ mExecutor.runAllReady();
+
+ verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_ACTION_SHOWN, 0, "actionShownSource");
+ verifyNoMoreInteractions(mUiEventLogger);
+ }
+
+ // start of refactored setClipData tests
+ @Test
public void test_setClipData_nullData() {
ClipData clipData = null;
mOverlayController.setClipData(clipData, "");
@@ -280,4 +442,43 @@
verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_ACTION_SHOWN, 0, "actionShownSource");
verifyNoMoreInteractions(mUiEventLogger);
}
+
+ @Test
+ public void test_noInsets_showsExpanded() {
+ mOverlayController.setClipData(mSampleClipData, "");
+
+ verify(mClipboardOverlayView, never()).setMinimized(true);
+ verify(mClipboardOverlayView).setMinimized(false);
+ verify(mClipboardOverlayView).showTextPreview("Test Item", false);
+ }
+
+ @Test
+ public void test_insets_showsMinimized() {
+ when(mClipboardOverlayWindow.getWindowInsets()).thenReturn(
+ getImeInsets(new Rect(0, 0, 0, 1)));
+ mOverlayController.setClipData(mSampleClipData, "");
+
+ verify(mClipboardOverlayView).setMinimized(true);
+ verify(mClipboardOverlayView, never()).setMinimized(false);
+ verify(mClipboardOverlayView, never()).showTextPreview(any(), anyBoolean());
+
+ mCallbacks.onMinimizedViewTapped();
+
+ verify(mClipboardOverlayView).setMinimized(false);
+ verify(mClipboardOverlayView).showTextPreview("Test Item", false);
+ }
+
+ @Test
+ public void test_insetsChanged_minimizes() {
+ mOverlayController.setClipData(mSampleClipData, "");
+ verify(mClipboardOverlayView, never()).setMinimized(true);
+
+ WindowInsets insetsWithKeyboard = getImeInsets(new Rect(0, 0, 0, 1));
+ mOverlayController.onInsetsChanged(insetsWithKeyboard, ORIENTATION_PORTRAIT);
+ verify(mClipboardOverlayView).setMinimized(true);
+ }
+
+ private static WindowInsets getImeInsets(Rect r) {
+ return new WindowInsets.Builder().setInsets(WindowInsets.Type.ime(), Insets.of(r)).build();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
new file mode 100644
index 0000000..2ed0346
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.common.ui.view;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link SeekBarWithIconButtonsView}
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class SeekBarWithIconButtonsViewTest extends SysuiTestCase {
+
+ private ImageView mIconStart;
+ private ImageView mIconEnd;
+ private SeekBar mSeekbar;
+ private SeekBarWithIconButtonsView mIconDiscreteSliderLinearLayout;
+
+ @Before
+ public void setUp() {
+ mIconDiscreteSliderLinearLayout = new SeekBarWithIconButtonsView(mContext);
+ mIconStart = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_start);
+ mIconEnd = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_end);
+ mSeekbar = mIconDiscreteSliderLinearLayout.findViewById(R.id.seekbar);
+ }
+
+ @Test
+ public void setSeekBarProgressZero_startIconAndFrameDisabled() {
+ mIconDiscreteSliderLinearLayout.setProgress(0);
+
+ assertThat(mIconStart.isEnabled()).isFalse();
+ assertThat(mIconEnd.isEnabled()).isTrue();
+ }
+
+ @Test
+ public void setSeekBarProgressMax_endIconAndFrameDisabled() {
+ mIconDiscreteSliderLinearLayout.setProgress(mSeekbar.getMax());
+
+ assertThat(mIconEnd.isEnabled()).isFalse();
+ assertThat(mIconStart.isEnabled()).isTrue();
+ }
+
+ @Test
+ public void setSeekBarProgressMax_allIconsAndFramesEnabled() {
+ // We are using the default value for the max of seekbar.
+ // Therefore, the max value will be DEFAULT_SEEKBAR_MAX = 6.
+ mIconDiscreteSliderLinearLayout.setProgress(1);
+
+ assertThat(mIconStart.isEnabled()).isTrue();
+ assertThat(mIconEnd.isEnabled()).isTrue();
+ }
+
+ @Test
+ public void clickIconEnd_currentProgressIsOneToMax_reachesMax() {
+ mIconDiscreteSliderLinearLayout.setProgress(mSeekbar.getMax() - 1);
+ mIconEnd.performClick();
+
+ assertThat(mSeekbar.getProgress()).isEqualTo(mSeekbar.getMax());
+ }
+
+ @Test
+ public void clickIconStart_currentProgressIsOne_reachesZero() {
+ mIconDiscreteSliderLinearLayout.setProgress(1);
+ mIconStart.performClick();
+
+ assertThat(mSeekbar.getProgress()).isEqualTo(0);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index d54babf..e35b2a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -104,8 +104,6 @@
ArgumentCaptor<ControlsBindingController.LoadCallback>
@Captor
- private lateinit var userTrackerCallbackCaptor: ArgumentCaptor<UserTracker.Callback>
- @Captor
private lateinit var listingCallbackCaptor:
ArgumentCaptor<ControlsListingController.ControlsListingCallback>
@@ -178,10 +176,6 @@
)
controller.auxiliaryPersistenceWrapper = auxiliaryPersistenceWrapper
- verify(userTracker).addCallback(
- capture(userTrackerCallbackCaptor), any()
- )
-
verify(listingController).addCallback(capture(listingCallbackCaptor))
}
@@ -539,7 +533,7 @@
reset(persistenceWrapper)
- userTrackerCallbackCaptor.value.onUserChanged(otherUser, mContext)
+ controller.changeUser(UserHandle.of(otherUser))
verify(persistenceWrapper).changeFileAndBackupManager(any(), any())
verify(persistenceWrapper).readFavorites()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
new file mode 100644
index 0000000..7ecaca6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
@@ -0,0 +1,281 @@
+/*
+ * 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.controls.start
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.ServiceInfo
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.SelectedItem
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.FakeSystemClock
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsStartableTest : SysuiTestCase() {
+
+ @Mock private lateinit var controlsController: ControlsController
+ @Mock private lateinit var controlsListingController: ControlsListingController
+ @Mock private lateinit var userTracker: UserTracker
+
+ private lateinit var fakeExecutor: FakeExecutor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ context.orCreateTestableResources.addOverride(
+ R.array.config_controlsPreferredPackages,
+ arrayOf<String>()
+ )
+
+ fakeExecutor = FakeExecutor(FakeSystemClock())
+ }
+
+ @Test
+ fun testDisabledNothingIsCalled() {
+ createStartable(enabled = false).start()
+
+ verifyZeroInteractions(controlsController, controlsListingController, userTracker)
+ }
+
+ @Test
+ fun testNoPreferredPackagesNoDefaultSelected_noNewSelection() {
+ `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+ val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+ `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController, never()).setPreferredSelection(any())
+ }
+
+ @Test
+ fun testPreferredPackagesNotInstalled_noNewSelection() {
+ context.orCreateTestableResources.addOverride(
+ R.array.config_controlsPreferredPackages,
+ arrayOf(TEST_PACKAGE_PANEL)
+ )
+ `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+ `when`(controlsListingController.getCurrentServices()).thenReturn(emptyList())
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController, never()).setPreferredSelection(any())
+ }
+
+ @Test
+ fun testPreferredPackageNotPanel_noNewSelection() {
+ context.orCreateTestableResources.addOverride(
+ R.array.config_controlsPreferredPackages,
+ arrayOf(TEST_PACKAGE_PANEL)
+ )
+ `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+ val listings = listOf(ControlsServiceInfo(TEST_COMPONENT, "not panel", hasPanel = false))
+ `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController, never()).setPreferredSelection(any())
+ }
+
+ @Test
+ fun testExistingSelection_noNewSelection() {
+ context.orCreateTestableResources.addOverride(
+ R.array.config_controlsPreferredPackages,
+ arrayOf(TEST_PACKAGE_PANEL)
+ )
+ `when`(controlsController.getPreferredSelection())
+ .thenReturn(mock<SelectedItem.PanelItem>())
+ val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+ `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController, never()).setPreferredSelection(any())
+ }
+
+ @Test
+ fun testPanelAdded() {
+ context.orCreateTestableResources.addOverride(
+ R.array.config_controlsPreferredPackages,
+ arrayOf(TEST_PACKAGE_PANEL)
+ )
+ `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+ val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+ `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController).setPreferredSelection(listings[0].toPanelItem())
+ }
+
+ @Test
+ fun testMultiplePreferredOnlyOnePanel_panelAdded() {
+ context.orCreateTestableResources.addOverride(
+ R.array.config_controlsPreferredPackages,
+ arrayOf("other_package", TEST_PACKAGE_PANEL)
+ )
+ `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+ val listings =
+ listOf(
+ ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true),
+ ControlsServiceInfo(ComponentName("other_package", "cls"), "non panel", false)
+ )
+ `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController).setPreferredSelection(listings[0].toPanelItem())
+ }
+
+ @Test
+ fun testMultiplePreferredMultiplePanels_firstPreferredAdded() {
+ context.orCreateTestableResources.addOverride(
+ R.array.config_controlsPreferredPackages,
+ arrayOf(TEST_PACKAGE_PANEL, "other_package")
+ )
+ `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+ val listings =
+ listOf(
+ ControlsServiceInfo(ComponentName("other_package", "cls"), "panel", true),
+ ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true)
+ )
+ `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController).setPreferredSelection(listings[1].toPanelItem())
+ }
+
+ @Test
+ fun testPreferredSelectionIsPanel_bindOnStart() {
+ val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+ `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+ `when`(controlsController.getPreferredSelection()).thenReturn(listings[0].toPanelItem())
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController).bindComponentForPanel(TEST_COMPONENT_PANEL)
+ }
+
+ @Test
+ fun testPreferredSelectionPanel_listingNoPanel_notBind() {
+ val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = false))
+ `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+ `when`(controlsController.getPreferredSelection())
+ .thenReturn(SelectedItem.PanelItem("panel", TEST_COMPONENT_PANEL))
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController, never()).bindComponentForPanel(any())
+ }
+
+ @Test
+ fun testNotPanelSelection_noBind() {
+ val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = false))
+ `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+ `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+
+ createStartable(enabled = true).start()
+
+ verify(controlsController, never()).bindComponentForPanel(any())
+ }
+
+ private fun createStartable(enabled: Boolean): ControlsStartable {
+ val component: ControlsComponent =
+ mock() {
+ `when`(isEnabled()).thenReturn(enabled)
+ if (enabled) {
+ `when`(getControlsController()).thenReturn(Optional.of(controlsController))
+ `when`(getControlsListingController())
+ .thenReturn(Optional.of(controlsListingController))
+ } else {
+ `when`(getControlsController()).thenReturn(Optional.empty())
+ `when`(getControlsListingController()).thenReturn(Optional.empty())
+ }
+ }
+ return ControlsStartable(context.resources, fakeExecutor, component, userTracker)
+ }
+
+ private fun ControlsServiceInfo(
+ componentName: ComponentName,
+ label: CharSequence,
+ hasPanel: Boolean
+ ): ControlsServiceInfo {
+ val serviceInfo =
+ ServiceInfo().apply {
+ applicationInfo = ApplicationInfo()
+ packageName = componentName.packageName
+ name = componentName.className
+ }
+ return FakeControlsServiceInfo(context, serviceInfo, label, hasPanel)
+ }
+
+ private class FakeControlsServiceInfo(
+ context: Context,
+ serviceInfo: ServiceInfo,
+ private val label: CharSequence,
+ hasPanel: Boolean
+ ) : ControlsServiceInfo(context, serviceInfo) {
+
+ init {
+ if (hasPanel) {
+ panelActivity = serviceInfo.componentName
+ }
+ }
+
+ override fun loadLabel(): CharSequence {
+ return label
+ }
+ }
+
+ companion object {
+ private fun ControlsServiceInfo.toPanelItem(): SelectedItem.PanelItem {
+ if (panelActivity == null) {
+ throw IllegalArgumentException("$this is not a panel")
+ }
+ return SelectedItem.PanelItem(loadLabel(), componentName)
+ }
+
+ private const val TEST_PACKAGE = "pkg"
+ private val TEST_COMPONENT = ComponentName(TEST_PACKAGE, "service")
+ private const val TEST_PACKAGE_PANEL = "pkg.panel"
+ private val TEST_COMPONENT_PANEL = ComponentName(TEST_PACKAGE_PANEL, "service")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index b7d0f29..0f25764 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -289,4 +289,36 @@
verify(mCallback, times(1)).onStateChanged();
assertThat(stateController.areEntryAnimationsFinished()).isTrue();
}
+
+ @Test
+ public void testNotifyDreamOverlayStatusBarVisibleChanged() {
+ final DreamOverlayStateController stateController =
+ new DreamOverlayStateController(mExecutor, true);
+
+ stateController.addCallback(mCallback);
+ mExecutor.runAllReady();
+ assertThat(stateController.isDreamOverlayStatusBarVisible()).isFalse();
+
+ stateController.setDreamOverlayStatusBarVisible(true);
+ mExecutor.runAllReady();
+
+ verify(mCallback, times(1)).onStateChanged();
+ assertThat(stateController.isDreamOverlayStatusBarVisible()).isTrue();
+ }
+
+ @Test
+ public void testNotifyHasAssistantAttentionChanged() {
+ final DreamOverlayStateController stateController =
+ new DreamOverlayStateController(mExecutor, true);
+
+ stateController.addCallback(mCallback);
+ mExecutor.runAllReady();
+ assertThat(stateController.hasAssistantAttention()).isFalse();
+
+ stateController.setHasAssistantAttention(true);
+ mExecutor.runAllReady();
+
+ verify(mCallback, times(1)).onStateChanged();
+ assertThat(stateController.hasAssistantAttention()).isTrue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index 596b903..d16b757 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -468,6 +468,21 @@
}
@Test
+ public void testAssistantAttentionIconShownWhenAttentionGained() {
+ mController.onViewAttached();
+
+ when(mDreamOverlayStateController.hasAssistantAttention()).thenReturn(true);
+
+ final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
+ callbackCapture.getValue().onStateChanged();
+
+ verify(mView).showIcon(
+ DreamOverlayStatusBarView.STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE, true, null);
+ }
+
+ @Test
public void testStatusBarHiddenWhenSystemStatusBarShown() {
mController.onViewAttached();
@@ -572,6 +587,13 @@
assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
}
+ @Test
+ public void testDreamOverlayStatusBarVisibleSetToFalseOnDetach() {
+ mController.onViewAttached();
+ mController.onViewDetached();
+ verify(mDreamOverlayStateController).setDreamOverlayStatusBarVisible(false);
+ }
+
private StatusBarWindowStateListener updateStatusBarWindowState(boolean show) {
when(mStatusBarWindowStateController.windowIsShowing()).thenReturn(show);
final ArgumentCaptor<StatusBarWindowStateListener>
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
new file mode 100644
index 0000000..ef1061f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.dreams.conditions;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.app.AssistUtils;
+import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.shared.condition.Condition;
+
+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;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class AssistantAttentionConditionTest extends SysuiTestCase {
+ @Mock
+ Condition.Callback mCallback;
+ @Mock
+ AssistUtils mAssistUtils;
+ @Mock
+ DreamOverlayStateController mDreamOverlayStateController;
+
+ private AssistantAttentionCondition mAssistantAttentionCondition;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mAssistantAttentionCondition =
+ new AssistantAttentionCondition(mDreamOverlayStateController, mAssistUtils);
+ // Adding a callback also starts the condition.
+ mAssistantAttentionCondition.addCallback(mCallback);
+ }
+
+ @Test
+ public void testEnableVisualQueryDetection() {
+ final ArgumentCaptor<DreamOverlayStateController.Callback> argumentCaptor =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(argumentCaptor.capture());
+
+ when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(true);
+ argumentCaptor.getValue().onStateChanged();
+
+ verify(mAssistUtils).enableVisualQueryDetection(any());
+ }
+
+ @Test
+ public void testDisableVisualQueryDetection() {
+ final ArgumentCaptor<DreamOverlayStateController.Callback> argumentCaptor =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(argumentCaptor.capture());
+
+ when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(true);
+ argumentCaptor.getValue().onStateChanged();
+ when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(false);
+ argumentCaptor.getValue().onStateChanged();
+
+ verify(mAssistUtils).disableVisualQueryDetection();
+ }
+
+ @Test
+ public void testAttentionChangedTriggersCondition() throws RemoteException {
+ final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(callbackCaptor.capture());
+
+ when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(true);
+ callbackCaptor.getValue().onStateChanged();
+
+ final ArgumentCaptor<IVisualQueryDetectionAttentionListener> listenerCaptor =
+ ArgumentCaptor.forClass(IVisualQueryDetectionAttentionListener.class);
+ verify(mAssistUtils).enableVisualQueryDetection(listenerCaptor.capture());
+
+ listenerCaptor.getValue().onAttentionGained();
+ assertThat(mAssistantAttentionCondition.isConditionMet()).isTrue();
+
+ listenerCaptor.getValue().onAttentionLost();
+ assertThat(mAssistantAttentionCondition.isConditionMet()).isFalse();
+
+ verify(mCallback, times(2)).onConditionChanged(eq(mAssistantAttentionCondition));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
index a3740d8..925c06f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
@@ -19,7 +19,6 @@
import android.content.Context
import android.media.AudioManager
-import androidx.lifecycle.LiveData
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.settings.UserFileManager
@@ -27,10 +26,12 @@
import com.android.systemui.util.RingerModeTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Before
@@ -57,13 +58,15 @@
@Mock
private lateinit var userFileManager: UserFileManager
+ private lateinit var testDispatcher: TestDispatcher
private lateinit var testScope: TestScope
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- testScope = TestScope()
+ testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
whenever(userTracker.userContext).thenReturn(context)
whenever(userFileManager.getSharedPreferences(any(), any(), any()))
@@ -74,7 +77,10 @@
userTracker,
userFileManager,
ringerModeTracker,
- audioManager
+ audioManager,
+ testScope.backgroundScope,
+ testDispatcher,
+ testDispatcher,
)
}
@@ -103,17 +109,16 @@
}
@Test
- fun `triggered - state was previously NORMAL - currently SILENT - move to previous state`() {
+ fun `triggered - state was previously NORMAL - currently SILENT - move to previous state`() = testScope.runTest {
//given
val ringerModeCapture = argumentCaptor<Int>()
- val ringerModeInternal = mock<LiveData<Int>>()
- whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
- whenever(ringerModeInternal.value).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+ whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
underTest.onTriggered(null)
- whenever(ringerModeInternal.value).thenReturn(AudioManager.RINGER_MODE_SILENT)
+ whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT)
//when
val result = underTest.onTriggered(null)
+ runCurrent()
verify(audioManager, times(2)).ringerModeInternal = ringerModeCapture.capture()
//then
@@ -122,15 +127,14 @@
}
@Test
- fun `triggered - state is not SILENT - move to SILENT ringer`() {
+ fun `triggered - state is not SILENT - move to SILENT ringer`() = testScope.runTest {
//given
val ringerModeCapture = argumentCaptor<Int>()
- val ringerModeInternal = mock<LiveData<Int>>()
- whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
- whenever(ringerModeInternal.value).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+ whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
//when
val result = underTest.onTriggered(null)
+ runCurrent()
verify(audioManager).ringerModeInternal = ringerModeCapture.capture()
//then
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
index 26601b6..34f3ed8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
@@ -37,6 +37,8 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -67,6 +69,7 @@
@Mock
private lateinit var keyguardQuickAffordanceRepository: KeyguardQuickAffordanceRepository
+ private lateinit var testDispatcher: TestDispatcher
private lateinit var testScope: TestScope
private lateinit var underTest: MuteQuickAffordanceCoreStartable
@@ -83,7 +86,8 @@
val emission = MutableStateFlow(mapOf("testQuickAffordanceKey" to listOf(config)))
whenever(keyguardQuickAffordanceRepository.selections).thenReturn(emission)
- testScope = TestScope()
+ testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
underTest = MuteQuickAffordanceCoreStartable(
featureFlags,
@@ -91,7 +95,8 @@
ringerModeTracker,
userFileManager,
keyguardQuickAffordanceRepository,
- testScope,
+ testScope.backgroundScope,
+ testDispatcher,
)
}
@@ -158,6 +163,7 @@
runCurrent()
verify(ringerModeInternal).observeForever(observerCaptor.capture())
observerCaptor.value.onChanged(newRingerMode)
+ runCurrent()
val result = sharedPrefs.getInt("key_last_non_silent_ringer_mode", -1)
//then
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricRepositoryTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index 4aac7d0..ddd1049 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -33,7 +33,6 @@
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -48,12 +47,11 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidTestingRunner::class)
-class BiometricRepositoryTest : SysuiTestCase() {
- private lateinit var underTest: BiometricRepository
+class BiometricSettingsRepositoryTest : SysuiTestCase() {
+ private lateinit var underTest: BiometricSettingsRepository
@Mock private lateinit var authController: AuthController
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@@ -73,11 +71,11 @@
userRepository = FakeUserRepository()
}
- private suspend fun createBiometricRepository() {
+ private suspend fun createBiometricSettingsRepository() {
userRepository.setUserInfos(listOf(PRIMARY_USER))
userRepository.setSelectedUserInfo(PRIMARY_USER)
underTest =
- BiometricRepositoryImpl(
+ BiometricSettingsRepositoryImpl(
context = context,
lockPatternUtils = lockPatternUtils,
broadcastDispatcher = fakeBroadcastDispatcher,
@@ -93,7 +91,7 @@
@Test
fun fingerprintEnrollmentChange() =
testScope.runTest {
- createBiometricRepository()
+ createBiometricSettingsRepository()
val fingerprintEnabledByDevicePolicy = collectLastValue(underTest.isFingerprintEnrolled)
runCurrent()
@@ -119,7 +117,7 @@
@Test
fun strongBiometricAllowedChange() =
testScope.runTest {
- createBiometricRepository()
+ createBiometricSettingsRepository()
val strongBiometricAllowed = collectLastValue(underTest.isStrongBiometricAllowed)
runCurrent()
@@ -142,7 +140,7 @@
@Test
fun fingerprintDisabledByDpmChange() =
testScope.runTest {
- createBiometricRepository()
+ createBiometricSettingsRepository()
val fingerprintEnabledByDevicePolicy =
collectLastValue(underTest.isFingerprintEnabledByDevicePolicy)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
index 2bc2ea1..03cb1db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -47,7 +47,7 @@
MockitoAnnotations.initMocks(this)
val testCoroutineScope = TestCoroutineScope()
underTest =
- KeyguardBouncerRepository(
+ KeyguardBouncerRepositoryImpl(
viewMediatorCallback,
systemClock,
testCoroutineScope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
index 68fff26..8caf60f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -22,9 +22,10 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeBiometricRepository
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.time.SystemClock
@@ -46,7 +47,7 @@
class AlternateBouncerInteractorTest : SysuiTestCase() {
private lateinit var underTest: AlternateBouncerInteractor
private lateinit var bouncerRepository: KeyguardBouncerRepository
- private lateinit var biometricRepository: FakeBiometricRepository
+ private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
private lateinit var deviceEntryFingerprintAuthRepository:
FakeDeviceEntryFingerprintAuthRepository
@Mock private lateinit var systemClock: SystemClock
@@ -58,19 +59,19 @@
fun setup() {
MockitoAnnotations.initMocks(this)
bouncerRepository =
- KeyguardBouncerRepository(
+ KeyguardBouncerRepositoryImpl(
mock(ViewMediatorCallback::class.java),
FakeSystemClock(),
TestCoroutineScope(),
bouncerLogger,
)
- biometricRepository = FakeBiometricRepository()
+ biometricSettingsRepository = FakeBiometricSettingsRepository()
deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
featureFlags = FakeFeatureFlags().apply { this.set(Flags.MODERN_ALTERNATE_BOUNCER, true) }
underTest =
AlternateBouncerInteractor(
bouncerRepository,
- biometricRepository,
+ biometricSettingsRepository,
deviceEntryFingerprintAuthRepository,
systemClock,
keyguardUpdateMonitor,
@@ -95,7 +96,7 @@
@Test
fun canShowAlternateBouncerForFingerprint_noFingerprintsEnrolled() {
givenCanShowAlternateBouncer()
- biometricRepository.setFingerprintEnrolled(false)
+ biometricSettingsRepository.setFingerprintEnrolled(false)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@@ -103,7 +104,7 @@
@Test
fun canShowAlternateBouncerForFingerprint_strongBiometricNotAllowed() {
givenCanShowAlternateBouncer()
- biometricRepository.setStrongBiometricAllowed(false)
+ biometricSettingsRepository.setStrongBiometricAllowed(false)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@@ -111,7 +112,7 @@
@Test
fun canShowAlternateBouncerForFingerprint_devicePolicyDoesNotAllowFingerprint() {
givenCanShowAlternateBouncer()
- biometricRepository.setFingerprintEnabledByDevicePolicy(false)
+ biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(false)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@@ -158,13 +159,13 @@
private fun givenCanShowAlternateBouncer() {
bouncerRepository.setAlternateBouncerUIAvailable(true)
- biometricRepository.setFingerprintEnrolled(true)
- biometricRepository.setStrongBiometricAllowed(true)
- biometricRepository.setFingerprintEnabledByDevicePolicy(true)
+ biometricSettingsRepository.setFingerprintEnrolled(true)
+ biometricSettingsRepository.setStrongBiometricAllowed(true)
+ biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
deviceEntryFingerprintAuthRepository.setLockedOut(false)
}
private fun givenCannotShowAlternateBouncer() {
- biometricRepository.setFingerprintEnrolled(false)
+ biometricSettingsRepository.setFingerprintEnrolled(false)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 3a871b4..702f3763 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositoryImpl
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -437,6 +438,43 @@
}
@Test
+ fun `DOZING to GONE`() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to DOZING
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DOZING,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ runCurrent()
+ reset(mockTransitionRepository)
+
+ // WHEN biometrics succeeds with wake and unlock mode
+ keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture())
+ }
+ // THEN a transition to DOZING should occur
+ assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.DOZING)
+ assertThat(info.to).isEqualTo(KeyguardState.GONE)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun `GONE to DOZING`() =
testScope.runTest {
// GIVEN a device with AOD not available
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index 7f48ea1..c5e0252 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -68,13 +68,13 @@
@Mock private lateinit var keyguardBypassController: KeyguardBypassController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
private val mainHandler = FakeHandler(Looper.getMainLooper())
- private lateinit var mPrimaryBouncerInteractor: PrimaryBouncerInteractor
+ private lateinit var underTest: PrimaryBouncerInteractor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
DejankUtils.setImmediate(true)
- mPrimaryBouncerInteractor =
+ underTest =
PrimaryBouncerInteractor(
repository,
bouncerView,
@@ -94,7 +94,7 @@
@Test
fun testShow_isScrimmed() {
- mPrimaryBouncerInteractor.show(true)
+ underTest.show(true)
verify(repository).setOnScreenTurnedOff(false)
verify(repository).setKeyguardAuthenticated(null)
verify(repository).setPrimaryHide(false)
@@ -124,7 +124,7 @@
@Test
fun testHide() {
- mPrimaryBouncerInteractor.hide()
+ underTest.hide()
verify(falsingCollector).onBouncerHidden()
verify(keyguardStateController).notifyBouncerShowing(false)
verify(repository).setPrimaryShowingSoon(false)
@@ -137,7 +137,7 @@
@Test
fun testExpansion() {
`when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
- mPrimaryBouncerInteractor.setPanelExpansion(0.6f)
+ underTest.setPanelExpansion(0.6f)
verify(repository).setPanelExpansion(0.6f)
verify(mPrimaryBouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
}
@@ -146,7 +146,7 @@
fun testExpansion_fullyShown() {
`when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
- mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_VISIBLE)
+ underTest.setPanelExpansion(EXPANSION_VISIBLE)
verify(falsingCollector).onBouncerShown()
verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown()
}
@@ -155,7 +155,7 @@
fun testExpansion_fullyHidden() {
`when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
- mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN)
+ underTest.setPanelExpansion(EXPANSION_HIDDEN)
verify(repository).setPrimaryVisible(false)
verify(repository).setPrimaryShow(null)
verify(repository).setPrimaryHide(true)
@@ -167,7 +167,7 @@
@Test
fun testExpansion_startingToHide() {
`when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
- mPrimaryBouncerInteractor.setPanelExpansion(0.1f)
+ underTest.setPanelExpansion(0.1f)
verify(repository).setPrimaryStartingToHide(true)
verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToHide()
}
@@ -175,7 +175,7 @@
@Test
fun testShowMessage() {
val argCaptor = ArgumentCaptor.forClass(BouncerShowMessageModel::class.java)
- mPrimaryBouncerInteractor.showMessage("abc", null)
+ underTest.showMessage("abc", null)
verify(repository).setShowMessage(argCaptor.capture())
assertThat(argCaptor.value.message).isEqualTo("abc")
}
@@ -184,62 +184,62 @@
fun testDismissAction() {
val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
val cancelAction = mock(Runnable::class.java)
- mPrimaryBouncerInteractor.setDismissAction(onDismissAction, cancelAction)
+ underTest.setDismissAction(onDismissAction, cancelAction)
verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
}
@Test
fun testUpdateResources() {
- mPrimaryBouncerInteractor.updateResources()
+ underTest.updateResources()
verify(repository).setResourceUpdateRequests(true)
}
@Test
fun testNotifyKeyguardAuthenticated() {
- mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(true)
+ underTest.notifyKeyguardAuthenticated(true)
verify(repository).setKeyguardAuthenticated(true)
}
@Test
fun testNotifyShowedMessage() {
- mPrimaryBouncerInteractor.onMessageShown()
+ underTest.onMessageShown()
verify(repository).setShowMessage(null)
}
@Test
fun testOnScreenTurnedOff() {
- mPrimaryBouncerInteractor.onScreenTurnedOff()
+ underTest.onScreenTurnedOff()
verify(repository).setOnScreenTurnedOff(true)
}
@Test
fun testSetKeyguardPosition() {
- mPrimaryBouncerInteractor.setKeyguardPosition(0f)
+ underTest.setKeyguardPosition(0f)
verify(repository).setKeyguardPosition(0f)
}
@Test
fun testNotifyKeyguardAuthenticatedHandled() {
- mPrimaryBouncerInteractor.notifyKeyguardAuthenticatedHandled()
+ underTest.notifyKeyguardAuthenticatedHandled()
verify(repository).setKeyguardAuthenticated(null)
}
@Test
fun testNotifyUpdatedResources() {
- mPrimaryBouncerInteractor.notifyUpdatedResources()
+ underTest.notifyUpdatedResources()
verify(repository).setResourceUpdateRequests(false)
}
@Test
fun testSetBackButtonEnabled() {
- mPrimaryBouncerInteractor.setBackButtonEnabled(true)
+ underTest.setBackButtonEnabled(true)
verify(repository).setIsBackButtonEnabled(true)
}
@Test
fun testStartDisappearAnimation() {
val runnable = mock(Runnable::class.java)
- mPrimaryBouncerInteractor.startDisappearAnimation(runnable)
+ underTest.startDisappearAnimation(runnable)
verify(repository).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
}
@@ -248,42 +248,42 @@
`when`(repository.primaryBouncerVisible.value).thenReturn(true)
`when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
- assertThat(mPrimaryBouncerInteractor.isFullyShowing()).isTrue()
+ assertThat(underTest.isFullyShowing()).isTrue()
`when`(repository.primaryBouncerVisible.value).thenReturn(false)
- assertThat(mPrimaryBouncerInteractor.isFullyShowing()).isFalse()
+ assertThat(underTest.isFullyShowing()).isFalse()
}
@Test
fun testIsScrimmed() {
`when`(repository.primaryBouncerScrimmed.value).thenReturn(true)
- assertThat(mPrimaryBouncerInteractor.isScrimmed()).isTrue()
+ assertThat(underTest.isScrimmed()).isTrue()
`when`(repository.primaryBouncerScrimmed.value).thenReturn(false)
- assertThat(mPrimaryBouncerInteractor.isScrimmed()).isFalse()
+ assertThat(underTest.isScrimmed()).isFalse()
}
@Test
fun testIsInTransit() {
`when`(repository.primaryBouncerShowingSoon.value).thenReturn(true)
- assertThat(mPrimaryBouncerInteractor.isInTransit()).isTrue()
+ assertThat(underTest.isInTransit()).isTrue()
`when`(repository.primaryBouncerShowingSoon.value).thenReturn(false)
- assertThat(mPrimaryBouncerInteractor.isInTransit()).isFalse()
+ assertThat(underTest.isInTransit()).isFalse()
`when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
- assertThat(mPrimaryBouncerInteractor.isInTransit()).isTrue()
+ assertThat(underTest.isInTransit()).isTrue()
}
@Test
fun testIsAnimatingAway() {
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
- assertThat(mPrimaryBouncerInteractor.isAnimatingAway()).isTrue()
+ assertThat(underTest.isAnimatingAway()).isTrue()
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
- assertThat(mPrimaryBouncerInteractor.isAnimatingAway()).isFalse()
+ assertThat(underTest.isAnimatingAway()).isFalse()
}
@Test
fun testWillDismissWithAction() {
`when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
- assertThat(mPrimaryBouncerInteractor.willDismissWithAction()).isTrue()
+ assertThat(underTest.willDismissWithAction()).isTrue()
`when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
- assertThat(mPrimaryBouncerInteractor.willDismissWithAction()).isFalse()
+ assertThat(underTest.willDismissWithAction()).isFalse()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
new file mode 100644
index 0000000..ea7bc91
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.os.Looper
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.BouncerViewDelegate
+import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.utils.os.FakeHandler
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() {
+ private lateinit var repository: FakeKeyguardBouncerRepository
+ @Mock private lateinit var bouncerView: BouncerView
+ @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+ @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
+ @Mock private lateinit var falsingCollector: FalsingCollector
+ @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
+ @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ private val mainHandler = FakeHandler(Looper.getMainLooper())
+ private lateinit var underTest: PrimaryBouncerInteractor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ repository = FakeKeyguardBouncerRepository()
+ underTest =
+ PrimaryBouncerInteractor(
+ repository,
+ bouncerView,
+ mainHandler,
+ keyguardStateController,
+ keyguardSecurityModel,
+ primaryBouncerCallbackInteractor,
+ falsingCollector,
+ dismissCallbackRegistry,
+ keyguardBypassController,
+ keyguardUpdateMonitor,
+ )
+ }
+
+ @Test
+ fun notInteractableWhenExpansionIsBelow90Percent() = runTest {
+ val isInteractable = collectLastValue(underTest.isInteractable)
+
+ repository.setPrimaryVisible(true)
+ repository.setPanelExpansion(0.15f)
+
+ assertThat(isInteractable()).isFalse()
+ }
+
+ @Test
+ fun notInteractableWhenExpansionAbove90PercentButNotVisible() = runTest {
+ val isInteractable = collectLastValue(underTest.isInteractable)
+
+ repository.setPrimaryVisible(false)
+ repository.setPanelExpansion(0.05f)
+
+ assertThat(isInteractable()).isFalse()
+ }
+
+ @Test
+ fun isInteractableWhenExpansionAbove90PercentAndVisible() = runTest {
+ var isInteractable = collectLastValue(underTest.isInteractable)
+
+ repository.setPrimaryVisible(true)
+ repository.setPanelExpansion(0.09f)
+
+ assertThat(isInteractable()).isTrue()
+
+ repository.setPanelExpansion(0.12f)
+ assertThat(isInteractable()).isFalse()
+
+ repository.setPanelExpansion(0f)
+ assertThat(isInteractable()).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
new file mode 100644
index 0000000..a5b78b74
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardTransitionAnimationFlowTest : SysuiTestCase() {
+ private lateinit var underTest: KeyguardTransitionAnimationFlow
+ private lateinit var repository: FakeKeyguardTransitionRepository
+
+ @Before
+ fun setUp() {
+ repository = FakeKeyguardTransitionRepository()
+ underTest =
+ KeyguardTransitionAnimationFlow(
+ 1000.milliseconds,
+ repository.transitions,
+ )
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun zeroDurationThrowsException() = runTest {
+ val flow = underTest.createFlow(duration = 0.milliseconds, onStep = { it })
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun startTimePlusDurationGreaterThanTransitionDurationThrowsException() = runTest {
+ val flow =
+ underTest.createFlow(
+ startTime = 300.milliseconds,
+ duration = 800.milliseconds,
+ onStep = { it }
+ )
+ }
+
+ @Test
+ fun onFinishRunsWhenSpecified() = runTest {
+ val flow =
+ underTest.createFlow(
+ duration = 100.milliseconds,
+ onStep = { it },
+ onFinish = { 10f },
+ )
+ var animationValues = collectLastValue(flow)
+ repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(animationValues()).isEqualTo(10f)
+ }
+
+ @Test
+ fun onCancelRunsWhenSpecified() = runTest {
+ val flow =
+ underTest.createFlow(
+ duration = 100.milliseconds,
+ onStep = { it },
+ onCancel = { 100f },
+ )
+ var animationValues = collectLastValue(flow)
+ repository.sendTransitionStep(step(0.5f, TransitionState.CANCELED))
+ assertThat(animationValues()).isEqualTo(100f)
+ }
+
+ @Test
+ fun usesStartTime() = runTest {
+ val flow =
+ underTest.createFlow(
+ startTime = 500.milliseconds,
+ duration = 500.milliseconds,
+ onStep = { it },
+ )
+ var animationValues = collectLastValue(flow)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(animationValues()).isEqualTo(0f)
+
+ // Should not emit a value
+ repository.sendTransitionStep(step(0.1f, TransitionState.RUNNING))
+
+ repository.sendTransitionStep(step(0.5f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 0f)
+ repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 0.2f)
+ repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 0.6f)
+ repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 1f)
+ }
+
+ @Test
+ fun usesInterpolator() = runTest {
+ val flow =
+ underTest.createFlow(
+ duration = 1000.milliseconds,
+ interpolator = EMPHASIZED_ACCELERATE,
+ onStep = { it },
+ )
+ var animationValues = collectLastValue(flow)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0f))
+ repository.sendTransitionStep(step(0.5f, TransitionState.RUNNING))
+ assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0.5f))
+ repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
+ assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0.6f))
+ repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
+ assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0.8f))
+ repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
+ assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(1f))
+ }
+
+ @Test
+ fun usesOnStepToDoubleValue() = runTest {
+ val flow =
+ underTest.createFlow(
+ duration = 1000.milliseconds,
+ onStep = { it * 2 },
+ )
+ var animationValues = collectLastValue(flow)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertFloat(animationValues(), 0f)
+ repository.sendTransitionStep(step(0.3f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 0.6f)
+ repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 1.2f)
+ repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 1.6f)
+ repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
+ assertFloat(animationValues(), 2f)
+ }
+
+ private fun assertFloat(actual: Float?, expected: Float) {
+ assertThat(actual!!).isWithin(0.01f).of(expected)
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.DREAMING,
+ value = value,
+ transitionState = state,
+ ownerName = "GoneToDreamingTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index 5571663..06e397d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -18,19 +18,13 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
-import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_OVERLAY_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_OVERLAY_TRANSLATION_Y
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -46,6 +40,7 @@
class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() {
private lateinit var underTest: DreamingToLockscreenTransitionViewModel
private lateinit var repository: FakeKeyguardTransitionRepository
+ private lateinit var transitionAnimation: KeyguardTransitionAnimationFlow
@Before
fun setUp() {
@@ -63,32 +58,18 @@
val job =
underTest.dreamOverlayTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
+ // Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
+ repository.sendTransitionStep(step(0.6f))
+ // ...up to here
+ repository.sendTransitionStep(step(0.8f))
repository.sendTransitionStep(step(1f))
- // Only 3 values should be present, since the dream overlay runs for a small fraction
- // of the overall animation time
- assertThat(values.size).isEqualTo(3)
- assertThat(values[0])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0f, DREAM_OVERLAY_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[1])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.3f, DREAM_OVERLAY_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[2])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.5f, DREAM_OVERLAY_TRANSLATION_Y)
- ) * pixels
- )
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
job.cancel()
}
@@ -100,16 +81,18 @@
val job = underTest.dreamOverlayAlpha.onEach { values.add(it) }.launchIn(this)
+ // Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f))
repository.sendTransitionStep(step(0.5f))
+ // ...up to here
repository.sendTransitionStep(step(1f))
// Only two values should be present, since the dream overlay runs for a small fraction
// of the overall animation time
- assertThat(values.size).isEqualTo(2)
- assertThat(values[0]).isEqualTo(1f - animValue(0f, DREAM_OVERLAY_ALPHA))
- assertThat(values[1]).isEqualTo(1f - animValue(0.1f, DREAM_OVERLAY_ALPHA))
+ assertThat(values.size).isEqualTo(4)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
job.cancel()
}
@@ -121,19 +104,15 @@
val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f))
- // Should start running here...
repository.sendTransitionStep(step(0.2f))
repository.sendTransitionStep(step(0.3f))
- // ...up to here
repository.sendTransitionStep(step(1f))
- // Only two values should be present, since the dream overlay runs for a small fraction
- // of the overall animation time
- assertThat(values.size).isEqualTo(2)
- assertThat(values[0]).isEqualTo(animValue(0.2f, LOCKSCREEN_ALPHA))
- assertThat(values[1]).isEqualTo(animValue(0.3f, LOCKSCREEN_ALPHA))
+ assertThat(values.size).isEqualTo(4)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
job.cancel()
}
@@ -147,58 +126,27 @@
val job =
underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
repository.sendTransitionStep(step(1f))
- assertThat(values.size).isEqualTo(4)
- assertThat(values[0])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(0f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[1])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[2])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[3])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(1f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
job.cancel()
}
- private fun animValue(stepValue: Float, params: AnimationParams): Float {
- val totalDuration = TO_LOCKSCREEN_DURATION
- val startValue = (params.startTime / totalDuration).toFloat()
-
- val multiplier = (totalDuration / params.duration).toFloat()
- return (stepValue - startValue) * multiplier
- }
-
- private fun step(value: Float): TransitionStep {
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
return TransitionStep(
from = KeyguardState.DREAMING,
to = KeyguardState.LOCKSCREEN,
value = value,
- transitionState = TransitionState.RUNNING,
+ transitionState = state,
ownerName = "DreamingToLockscreenTransitionViewModelTest"
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
index 7fa204b..14c3b50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
@@ -18,16 +18,12 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -59,20 +55,18 @@
val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
// Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f))
repository.sendTransitionStep(step(0.2f))
- // ...up to here
repository.sendTransitionStep(step(0.3f))
+ // ...up to here
repository.sendTransitionStep(step(1f))
// Only three values should be present, since the dream overlay runs for a small
- // fraction
- // of the overall animation time
- assertThat(values.size).isEqualTo(3)
- assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA))
- assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
- assertThat(values[2]).isEqualTo(1f - animValue(0.2f, LOCKSCREEN_ALPHA))
+ // fraction of the overall animation time
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
job.cancel()
}
@@ -87,45 +81,19 @@
underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
// Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
- // ...up to here
- repository.sendTransitionStep(step(1f))
// And a final reset event on CANCEL
repository.sendTransitionStep(step(0.8f, TransitionState.CANCELED))
- assertThat(values.size).isEqualTo(4)
- assertThat(values[0])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[1])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[2])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[3]).isEqualTo(0f)
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
+
job.cancel()
}
- private fun animValue(stepValue: Float, params: AnimationParams): Float {
- val totalDuration = TO_DREAMING_DURATION
- val startValue = (params.startTime / totalDuration).toFloat()
-
- val multiplier = (totalDuration / params.duration).toFloat()
- return (stepValue - startValue) * multiplier
- }
-
private fun step(
value: Float,
state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index 539fc2c..ed31dc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -18,16 +18,12 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -59,19 +55,18 @@
val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
// Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f))
repository.sendTransitionStep(step(0.2f))
- // ...up to here
repository.sendTransitionStep(step(0.3f))
+ // ...up to here
repository.sendTransitionStep(step(1f))
// Only three values should be present, since the dream overlay runs for a small
// fraction of the overall animation time
- assertThat(values.size).isEqualTo(3)
- assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA))
- assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
- assertThat(values[2]).isEqualTo(1f - animValue(0.2f, LOCKSCREEN_ALPHA))
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
job.cancel()
}
@@ -85,47 +80,22 @@
val job =
underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
- // Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
- // ...up to here
repository.sendTransitionStep(step(1f))
// And a final reset event on FINISHED
repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
- assertThat(values.size).isEqualTo(4)
- assertThat(values[0])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[1])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[2])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[3]).isEqualTo(0f)
+ assertThat(values.size).isEqualTo(6)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
+ // Validate finished value
+ assertThat(values[5]).isEqualTo(0f)
job.cancel()
}
- private fun animValue(stepValue: Float, params: AnimationParams): Float {
- val totalDuration = TO_DREAMING_DURATION
- val startValue = (params.startTime / totalDuration).toFloat()
-
- val multiplier = (totalDuration / params.duration).toFloat()
- return (stepValue - startValue) * multiplier
- }
-
private fun step(
value: Float,
state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index 759345f..458b315 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -18,16 +18,12 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -59,19 +55,18 @@
val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
// Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.1f))
repository.sendTransitionStep(step(0.4f))
- // ...up to here
repository.sendTransitionStep(step(0.7f))
+ // ...up to here
repository.sendTransitionStep(step(1f))
// Only 3 values should be present, since the dream overlay runs for a small fraction
// of the overall animation time
- assertThat(values.size).isEqualTo(3)
- assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA))
- assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
- assertThat(values[2]).isEqualTo(1f - animValue(0.4f, LOCKSCREEN_ALPHA))
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
job.cancel()
}
@@ -86,54 +81,51 @@
underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
// Should start running here...
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
repository.sendTransitionStep(step(1f))
// ...up to here
- assertThat(values.size).isEqualTo(4)
- assertThat(values[0])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[1])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[2])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[3])
- .isEqualTo(
- EMPHASIZED_ACCELERATE.getInterpolation(
- animValue(1f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
+
job.cancel()
}
- private fun animValue(stepValue: Float, params: AnimationParams): Float {
- val totalDuration = TO_OCCLUDED_DURATION
- val startValue = (params.startTime / totalDuration).toFloat()
+ @Test
+ fun lockscreenTranslationYIsCanceled() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
- val multiplier = (totalDuration / params.duration).toFloat()
- return (stepValue - startValue) * multiplier
- }
+ val pixels = 100
+ val job =
+ underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
- private fun step(value: Float): TransitionStep {
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0f))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.3f, TransitionState.CANCELED))
+
+ assertThat(values.size).isEqualTo(4)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
+
+ // Cancel will reset the translation
+ assertThat(values[3]).isEqualTo(0)
+
+ job.cancel()
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING,
+ ): TransitionStep {
return TransitionStep(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.OCCLUDED,
value = value,
- transitionState = TransitionState.RUNNING,
+ transitionState = state,
ownerName = "LockscreenToOccludedTransitionViewModelTest"
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
index 98d292d..a36214e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
@@ -18,16 +18,12 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -58,21 +54,19 @@
val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
- repository.sendTransitionStep(step(0f))
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.1f))
// Should start running here...
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.4f))
repository.sendTransitionStep(step(0.5f))
- // ...up to here
repository.sendTransitionStep(step(0.6f))
+ // ...up to here
+ repository.sendTransitionStep(step(0.8f))
repository.sendTransitionStep(step(1f))
- // Only two values should be present, since the dream overlay runs for a small fraction
- // of the overall animation time
- assertThat(values.size).isEqualTo(3)
- assertThat(values[0]).isEqualTo(animValue(0.3f, LOCKSCREEN_ALPHA))
- assertThat(values[1]).isEqualTo(animValue(0.4f, LOCKSCREEN_ALPHA))
- assertThat(values[2]).isEqualTo(animValue(0.5f, LOCKSCREEN_ALPHA))
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
job.cancel()
}
@@ -86,58 +80,27 @@
val job =
underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
repository.sendTransitionStep(step(0f))
repository.sendTransitionStep(step(0.3f))
repository.sendTransitionStep(step(0.5f))
repository.sendTransitionStep(step(1f))
- assertThat(values.size).isEqualTo(4)
- assertThat(values[0])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(0f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[1])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[2])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
- assertThat(values[3])
- .isEqualTo(
- -pixels +
- EMPHASIZED_DECELERATE.getInterpolation(
- animValue(1f, LOCKSCREEN_TRANSLATION_Y)
- ) * pixels
- )
+ assertThat(values.size).isEqualTo(5)
+ values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
job.cancel()
}
- private fun animValue(stepValue: Float, params: AnimationParams): Float {
- val totalDuration = TO_LOCKSCREEN_DURATION
- val startValue = (params.startTime / totalDuration).toFloat()
-
- val multiplier = (totalDuration / params.duration).toFloat()
- return (stepValue - startValue) * multiplier
- }
-
- private fun step(value: Float): TransitionStep {
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
return TransitionStep(
from = KeyguardState.OCCLUDED,
to = KeyguardState.LOCKSCREEN,
value = value,
- transitionState = TransitionState.RUNNING,
+ transitionState = state,
ownerName = "OccludedToLockscreenTransitionViewModelTest"
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt
index 1d6e980..670f117 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt
@@ -113,5 +113,6 @@
recommendations = emptyList(),
dismissIntent = null,
headphoneConnectionTimeMillis = 0,
- instanceId = InstanceId.fakeInstanceId(-1)
+ instanceId = InstanceId.fakeInstanceId(-1),
+ expiryTimeMs = 0,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
index c0639f3..0a5b124 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
@@ -79,7 +79,7 @@
USER_ID, true, APP, null, ARTIST, TITLE, null,
new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L,
- InstanceId.fakeInstanceId(-1), -1, false);
+ InstanceId.fakeInstanceId(-1), -1, false, null);
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME, null, false);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
index 9d33e6f..eb6235c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
@@ -27,11 +27,13 @@
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.ui.MediaPlayerData
+import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executor
@@ -40,11 +42,11 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
private const val KEY = "TEST_KEY"
@@ -72,6 +74,7 @@
@Mock private lateinit var smartspaceData: SmartspaceMediaData
@Mock private lateinit var smartspaceMediaRecommendationItem: SmartspaceAction
@Mock private lateinit var logger: MediaUiEventLogger
+ @Mock private lateinit var mediaFlags: MediaFlags
private lateinit var mediaDataFilter: MediaDataFilter
private lateinit var dataMain: MediaData
@@ -82,6 +85,7 @@
fun setup() {
MockitoAnnotations.initMocks(this)
MediaPlayerData.clear()
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
mediaDataFilter =
MediaDataFilter(
context,
@@ -90,7 +94,8 @@
lockscreenUserManager,
executor,
clock,
- logger
+ logger,
+ mediaFlags
)
mediaDataFilter.mediaDataManager = mediaDataManager
mediaDataFilter.addListener(listener)
@@ -108,19 +113,20 @@
)
dataGuest = dataMain.copy(userId = USER_GUEST)
- `when`(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY)
- `when`(smartspaceData.isActive).thenReturn(true)
- `when`(smartspaceData.isValid()).thenReturn(true)
- `when`(smartspaceData.packageName).thenReturn(SMARTSPACE_PACKAGE)
- `when`(smartspaceData.recommendations).thenReturn(listOf(smartspaceMediaRecommendationItem))
- `when`(smartspaceData.headphoneConnectionTimeMillis)
+ whenever(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY)
+ whenever(smartspaceData.isActive).thenReturn(true)
+ whenever(smartspaceData.isValid()).thenReturn(true)
+ whenever(smartspaceData.packageName).thenReturn(SMARTSPACE_PACKAGE)
+ whenever(smartspaceData.recommendations)
+ .thenReturn(listOf(smartspaceMediaRecommendationItem))
+ whenever(smartspaceData.headphoneConnectionTimeMillis)
.thenReturn(clock.currentTimeMillis() - 100)
- `when`(smartspaceData.instanceId).thenReturn(SMARTSPACE_INSTANCE_ID)
+ whenever(smartspaceData.instanceId).thenReturn(SMARTSPACE_INSTANCE_ID)
}
private fun setUser(id: Int) {
- `when`(lockscreenUserManager.isCurrentProfile(anyInt())).thenReturn(false)
- `when`(lockscreenUserManager.isCurrentProfile(eq(id))).thenReturn(true)
+ whenever(lockscreenUserManager.isCurrentProfile(anyInt())).thenReturn(false)
+ whenever(lockscreenUserManager.isCurrentProfile(eq(id))).thenReturn(true)
mediaDataFilter.handleUserSwitched(id)
}
@@ -277,7 +283,7 @@
@Test
fun hasActiveMediaOrRecommendation_inactiveRecommendationSet_returnsFalse() {
- `when`(smartspaceData.isActive).thenReturn(false)
+ whenever(smartspaceData.isActive).thenReturn(false)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
@@ -285,7 +291,7 @@
@Test
fun hasActiveMediaOrRecommendation_invalidRecommendationSet_returnsFalse() {
- `when`(smartspaceData.isValid()).thenReturn(false)
+ whenever(smartspaceData.isValid()).thenReturn(false)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
@@ -293,8 +299,8 @@
@Test
fun hasActiveMediaOrRecommendation_activeAndValidRecommendationSet_returnsTrue() {
- `when`(smartspaceData.isActive).thenReturn(true)
- `when`(smartspaceData.isValid()).thenReturn(true)
+ whenever(smartspaceData.isActive).thenReturn(true)
+ whenever(smartspaceData.isValid()).thenReturn(true)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
@@ -349,7 +355,7 @@
@Test
fun testOnSmartspaceMediaDataLoaded_noMedia_inactiveRec_showsNothing() {
- `when`(smartspaceData.isActive).thenReturn(false)
+ whenever(smartspaceData.isActive).thenReturn(false)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
@@ -379,7 +385,7 @@
@Test
fun testOnSmartspaceMediaDataLoaded_noRecentMedia_inactiveRec_showsNothing() {
- `when`(smartspaceData.isActive).thenReturn(false)
+ whenever(smartspaceData.isActive).thenReturn(false)
val dataOld = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataOld)
@@ -395,7 +401,7 @@
@Test
fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_inactiveRec_showsNothing() {
- `when`(smartspaceData.isActive).thenReturn(false)
+ whenever(smartspaceData.isActive).thenReturn(false)
// WHEN we have media that was recently played, but not currently active
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
@@ -418,7 +424,7 @@
@Test
fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_activeInvalidRec_usesMedia() {
- `when`(smartspaceData.isValid()).thenReturn(false)
+ whenever(smartspaceData.isValid()).thenReturn(false)
// WHEN we have media that was recently played, but not currently active
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
@@ -513,4 +519,59 @@
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
}
+
+ @Test
+ fun testOnSmartspaceLoaded_persistentEnabled_isInactive_notifiesListeners() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ whenever(smartspaceData.isActive).thenReturn(false)
+
+ mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
+ assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
+ assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isTrue()
+ }
+
+ @Test
+ fun testOnSmartspaceLoaded_persistentEnabled_inactive_hasRecentMedia_staysInactive() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ whenever(smartspaceData.isActive).thenReturn(false)
+
+ // If there is media that was recently played but inactive
+ val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
+
+ // And an inactive recommendation is loaded
+ mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+
+ // Smartspace is loaded but the media stays inactive
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
+ verify(listener, never())
+ .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
+ assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
+ assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isTrue()
+ }
+
+ @Test
+ fun testOnSwipeToDismiss_persistentEnabled_recommendationSetInactive() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+
+ val data =
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+ targetId = SMARTSPACE_KEY,
+ isActive = true,
+ packageName = SMARTSPACE_PACKAGE,
+ recommendations = listOf(smartspaceMediaRecommendationItem),
+ )
+ mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, data)
+ mediaDataFilter.onSwipeToDismiss()
+
+ verify(mediaDataManager).setRecommendationInactive(eq(SMARTSPACE_KEY))
+ verify(mediaDataManager, never())
+ .dismissSmartspaceRecommendation(eq(SMARTSPACE_KEY), anyLong())
+ }
}
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 1ac6695..44e2fbd 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
@@ -46,6 +46,8 @@
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_SOURCE
+import com.android.systemui.media.controls.models.recommendation.EXTRA_VALUE_TRIGGER_PERIODIC
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider
import com.android.systemui.media.controls.resume.MediaResumeListener
@@ -82,6 +84,8 @@
private const val KEY = "KEY"
private const val KEY_2 = "KEY_2"
private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
+private const val SMARTSPACE_CREATION_TIME = 1234L
+private const val SMARTSPACE_EXPIRY_TIME = 5678L
private const val PACKAGE_NAME = "com.example.app"
private const val SYSTEM_PACKAGE_NAME = "com.android.systemui"
private const val APP_NAME = "SystemUI"
@@ -230,10 +234,12 @@
whenever(mediaSmartspaceTarget.smartspaceTargetId).thenReturn(KEY_MEDIA_SMARTSPACE)
whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA)
whenever(mediaSmartspaceTarget.iconGrid).thenReturn(validRecommendationList)
- whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L)
+ whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(SMARTSPACE_CREATION_TIME)
+ whenever(mediaSmartspaceTarget.expiryTimeMillis).thenReturn(SMARTSPACE_EXPIRY_TIME)
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false)
whenever(mediaFlags.isExplicitIndicatorEnabled()).thenReturn(true)
whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false)
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
}
@@ -644,27 +650,8 @@
build()
}
val currentTime = clock.elapsedRealtime()
- mediaDataManager.addResumptionControls(
- USER_ID,
- desc,
- Runnable {},
- session.sessionToken,
- APP_NAME,
- pendingIntent,
- PACKAGE_NAME
- )
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- // THEN the media data indicates that it is for resumption
- verify(listener)
- .onMediaDataLoaded(
- eq(PACKAGE_NAME),
- eq(null),
- capture(mediaDataCaptor),
- eq(true),
- eq(0),
- eq(false)
- )
+ addResumeControlAndLoad(desc)
+
val data = mediaDataCaptor.value
assertThat(data.resumption).isTrue()
assertThat(data.song).isEqualTo(SESSION_TITLE)
@@ -690,27 +677,8 @@
build()
}
val currentTime = clock.elapsedRealtime()
- mediaDataManager.addResumptionControls(
- USER_ID,
- desc,
- Runnable {},
- session.sessionToken,
- APP_NAME,
- pendingIntent,
- PACKAGE_NAME
- )
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- // THEN the media data indicates that it is for resumption
- verify(listener)
- .onMediaDataLoaded(
- eq(PACKAGE_NAME),
- eq(null),
- capture(mediaDataCaptor),
- eq(true),
- eq(0),
- eq(false)
- )
+ addResumeControlAndLoad(desc)
+
val data = mediaDataCaptor.value
assertThat(data.resumption).isTrue()
assertThat(data.song).isEqualTo(SESSION_TITLE)
@@ -723,6 +691,84 @@
}
@Test
+ fun testAddResumptionControls_hasPartialProgress() {
+ whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+
+ // WHEN resumption controls are added with partial progress
+ val progress = 0.5
+ val extras =
+ Bundle().apply {
+ putInt(
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
+ )
+ putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, progress)
+ }
+ val desc =
+ MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ setExtras(extras)
+ build()
+ }
+ addResumeControlAndLoad(desc)
+
+ val data = mediaDataCaptor.value
+ assertThat(data.resumption).isTrue()
+ assertThat(data.resumeProgress).isEqualTo(progress)
+ }
+
+ @Test
+ fun testAddResumptionControls_hasNotPlayedProgress() {
+ whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+
+ // WHEN resumption controls are added that have not been played
+ val extras =
+ Bundle().apply {
+ putInt(
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
+ )
+ }
+ val desc =
+ MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ setExtras(extras)
+ build()
+ }
+ addResumeControlAndLoad(desc)
+
+ val data = mediaDataCaptor.value
+ assertThat(data.resumption).isTrue()
+ assertThat(data.resumeProgress).isEqualTo(0)
+ }
+
+ @Test
+ fun testAddResumptionControls_hasFullProgress() {
+ whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+
+ // WHEN resumption controls are added with progress info
+ val extras =
+ Bundle().apply {
+ putInt(
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
+ )
+ }
+ val desc =
+ MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ setExtras(extras)
+ build()
+ }
+ addResumeControlAndLoad(desc)
+
+ // THEN the media data includes the progress
+ val data = mediaDataCaptor.value
+ assertThat(data.resumption).isTrue()
+ assertThat(data.resumeProgress).isEqualTo(1)
+ }
+
+ @Test
fun testResumptionDisabled_dismissesResumeControls() {
// WHEN there are resume controls and resumption is switched off
val desc =
@@ -730,26 +776,8 @@
setTitle(SESSION_TITLE)
build()
}
- mediaDataManager.addResumptionControls(
- USER_ID,
- desc,
- Runnable {},
- session.sessionToken,
- APP_NAME,
- pendingIntent,
- PACKAGE_NAME
- )
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener)
- .onMediaDataLoaded(
- eq(PACKAGE_NAME),
- eq(null),
- capture(mediaDataCaptor),
- eq(true),
- eq(0),
- eq(false)
- )
+ addResumeControlAndLoad(desc)
+
val data = mediaDataCaptor.value
mediaDataManager.setMediaResumptionEnabled(false)
@@ -825,8 +853,9 @@
cardAction = mediaSmartspaceBaseAction,
recommendations = validRecommendationList,
dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = 1234L,
- instanceId = InstanceId.fakeInstanceId(instanceId)
+ headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
+ instanceId = InstanceId.fakeInstanceId(instanceId),
+ expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
)
),
eq(false)
@@ -848,8 +877,9 @@
targetId = KEY_MEDIA_SMARTSPACE,
isActive = true,
dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = 1234L,
- instanceId = InstanceId.fakeInstanceId(instanceId)
+ headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
+ instanceId = InstanceId.fakeInstanceId(instanceId),
+ expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
)
),
eq(false)
@@ -879,8 +909,9 @@
targetId = KEY_MEDIA_SMARTSPACE,
isActive = true,
dismissIntent = null,
- headphoneConnectionTimeMillis = 1234L,
- instanceId = InstanceId.fakeInstanceId(instanceId)
+ headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
+ instanceId = InstanceId.fakeInstanceId(instanceId),
+ expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
)
),
eq(false)
@@ -909,6 +940,129 @@
}
@Test
+ fun testOnSmartspaceMediaDataLoaded_persistentEnabled_headphoneTrigger_isActive() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
+ val instanceId = instanceIdSequence.lastInstanceId
+
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(
+ eq(KEY_MEDIA_SMARTSPACE),
+ eq(
+ SmartspaceMediaData(
+ targetId = KEY_MEDIA_SMARTSPACE,
+ isActive = true,
+ packageName = PACKAGE_NAME,
+ cardAction = mediaSmartspaceBaseAction,
+ recommendations = validRecommendationList,
+ dismissIntent = DISMISS_INTENT,
+ headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
+ instanceId = InstanceId.fakeInstanceId(instanceId),
+ expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
+ )
+ ),
+ eq(false)
+ )
+ }
+
+ @Test
+ fun testOnSmartspaceMediaDataLoaded_persistentEnabled_periodicTrigger_notActive() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ val extras =
+ Bundle().apply {
+ putString("package_name", PACKAGE_NAME)
+ putParcelable("dismiss_intent", DISMISS_INTENT)
+ putString(EXTRA_KEY_TRIGGER_SOURCE, EXTRA_VALUE_TRIGGER_PERIODIC)
+ }
+ whenever(mediaSmartspaceBaseAction.extras).thenReturn(extras)
+
+ smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
+ val instanceId = instanceIdSequence.lastInstanceId
+
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(
+ eq(KEY_MEDIA_SMARTSPACE),
+ eq(
+ SmartspaceMediaData(
+ targetId = KEY_MEDIA_SMARTSPACE,
+ isActive = false,
+ packageName = PACKAGE_NAME,
+ cardAction = mediaSmartspaceBaseAction,
+ recommendations = validRecommendationList,
+ dismissIntent = DISMISS_INTENT,
+ headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
+ instanceId = InstanceId.fakeInstanceId(instanceId),
+ expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
+ )
+ ),
+ eq(false)
+ )
+ }
+
+ @Test
+ fun testOnSmartspaceMediaDataLoaded_persistentEnabled_noTargets_inactive() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+
+ smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
+ val instanceId = instanceIdSequence.lastInstanceId
+
+ smartspaceMediaDataProvider.onTargetsAvailable(listOf())
+ uiExecutor.advanceClockToLast()
+ uiExecutor.runAllReady()
+
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(
+ eq(KEY_MEDIA_SMARTSPACE),
+ eq(
+ SmartspaceMediaData(
+ targetId = KEY_MEDIA_SMARTSPACE,
+ isActive = false,
+ packageName = PACKAGE_NAME,
+ cardAction = mediaSmartspaceBaseAction,
+ recommendations = validRecommendationList,
+ dismissIntent = DISMISS_INTENT,
+ headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
+ instanceId = InstanceId.fakeInstanceId(instanceId),
+ expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
+ )
+ ),
+ eq(false)
+ )
+ verify(listener, never()).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
+ }
+
+ @Test
+ fun testSetRecommendationInactive_notifiesListeners() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+
+ smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
+ val instanceId = instanceIdSequence.lastInstanceId
+
+ mediaDataManager.setRecommendationInactive(KEY_MEDIA_SMARTSPACE)
+ uiExecutor.advanceClockToLast()
+ uiExecutor.runAllReady()
+
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(
+ eq(KEY_MEDIA_SMARTSPACE),
+ eq(
+ SmartspaceMediaData(
+ targetId = KEY_MEDIA_SMARTSPACE,
+ isActive = false,
+ packageName = PACKAGE_NAME,
+ cardAction = mediaSmartspaceBaseAction,
+ recommendations = validRecommendationList,
+ dismissIntent = DISMISS_INTENT,
+ headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
+ instanceId = InstanceId.fakeInstanceId(instanceId),
+ expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
+ )
+ ),
+ eq(false)
+ )
+ }
+
+ @Test
fun testOnSmartspaceMediaDataLoaded_settingDisabled_doesNothing() {
// WHEN media recommendation setting is off
Settings.Secure.putInt(
@@ -1690,4 +1844,29 @@
stateBuilder.setState(PlaybackState.STATE_PAUSED, 0, 1.0f)
whenever(controller.playbackState).thenReturn(stateBuilder.build())
}
+
+ /** Helper function to add a resumption control and capture the resulting MediaData */
+ private fun addResumeControlAndLoad(desc: MediaDescription) {
+ mediaDataManager.addResumptionControls(
+ USER_ID,
+ desc,
+ Runnable {},
+ session.sessionToken,
+ APP_NAME,
+ pendingIntent,
+ PACKAGE_NAME
+ )
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ }
}
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 92bf84c..8baa06a 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
@@ -25,13 +25,16 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.MediaTestUtils
import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -48,7 +51,6 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
private const val KEY = "KEY"
@@ -56,6 +58,7 @@
private const val SESSION_KEY = "SESSION_KEY"
private const val SESSION_ARTIST = "SESSION_ARTIST"
private const val SESSION_TITLE = "SESSION_TITLE"
+private const val SMARTSPACE_KEY = "SMARTSPACE_KEY"
private fun <T> anyObject(): T {
return Mockito.anyObject<T>()
@@ -85,10 +88,13 @@
private lateinit var resumeData: MediaData
private lateinit var mediaTimeoutListener: MediaTimeoutListener
private var clock = FakeSystemClock()
+ @Mock private lateinit var mediaFlags: MediaFlags
+ @Mock private lateinit var smartspaceData: SmartspaceMediaData
@Before
fun setup() {
- `when`(mediaControllerFactory.create(any())).thenReturn(mediaController)
+ whenever(mediaControllerFactory.create(any())).thenReturn(mediaController)
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
executor = FakeExecutor(clock)
mediaTimeoutListener =
MediaTimeoutListener(
@@ -96,7 +102,8 @@
executor,
logger,
statusBarStateController,
- clock
+ clock,
+ mediaFlags,
)
mediaTimeoutListener.timeoutCallback = timeoutCallback
mediaTimeoutListener.stateCallback = stateCallback
@@ -133,9 +140,9 @@
@Test
fun testOnMediaDataLoaded_registersPlaybackListener() {
val playingState = mock(android.media.session.PlaybackState::class.java)
- `when`(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
+ whenever(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
- `when`(mediaController.playbackState).thenReturn(playingState)
+ whenever(mediaController.playbackState).thenReturn(playingState)
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
verify(logger).logPlaybackState(eq(KEY), eq(playingState))
@@ -188,8 +195,8 @@
// To playing
val playingState = mock(android.media.session.PlaybackState::class.java)
- `when`(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
- `when`(mediaController.playbackState).thenReturn(playingState)
+ whenever(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
+ whenever(mediaController.playbackState).thenReturn(playingState)
mediaTimeoutListener.onMediaDataLoaded(newKey, KEY, mediaData)
verify(mediaController).unregisterCallback(anyObject())
verify(mediaController).registerCallback(anyObject())
@@ -208,8 +215,8 @@
// Migrate, still not playing
val playingState = mock(android.media.session.PlaybackState::class.java)
- `when`(playingState.state).thenReturn(PlaybackState.STATE_PAUSED)
- `when`(mediaController.playbackState).thenReturn(playingState)
+ whenever(playingState.state).thenReturn(PlaybackState.STATE_PAUSED)
+ whenever(mediaController.playbackState).thenReturn(playingState)
mediaTimeoutListener.onMediaDataLoaded(newKey, KEY, mediaData)
// The number of queued timeout tasks remains the same. The timeout task isn't cancelled nor
@@ -296,8 +303,8 @@
// WHEN we get an update with media playing
val playingState = mock(android.media.session.PlaybackState::class.java)
- `when`(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
- `when`(mediaController.playbackState).thenReturn(playingState)
+ whenever(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
+ whenever(mediaController.playbackState).thenReturn(playingState)
val mediaPlaying = mediaData.copy(isPlaying = true)
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaPlaying)
@@ -347,7 +354,7 @@
// WHEN regular media is paused
val pausedState =
PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
- `when`(mediaController.playbackState).thenReturn(pausedState)
+ whenever(mediaController.playbackState).thenReturn(pausedState)
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
assertThat(executor.numPending()).isEqualTo(1)
@@ -379,7 +386,7 @@
// AND that media is resumed
val playingState =
PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
- `when`(mediaController.playbackState).thenReturn(playingState)
+ whenever(mediaController.playbackState).thenReturn(playingState)
mediaTimeoutListener.onMediaDataLoaded(KEY, PACKAGE, mediaData)
// THEN the timeout length is changed to a regular media control
@@ -593,8 +600,91 @@
assertThat(executor.numPending()).isEqualTo(1)
}
+ @Test
+ fun testSmartspaceDataLoaded_schedulesTimeout() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ val duration = 60_000
+ val createTime = 1234L
+ val expireTime = createTime + duration
+ whenever(smartspaceData.headphoneConnectionTimeMillis).thenReturn(createTime)
+ whenever(smartspaceData.expiryTimeMs).thenReturn(expireTime)
+
+ mediaTimeoutListener.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+ assertThat(executor.numPending()).isEqualTo(1)
+ assertThat(executor.advanceClockToNext()).isEqualTo(duration)
+ }
+
+ @Test
+ fun testSmartspaceMediaData_timesOut_invokesCallback() {
+ // Given a pending timeout
+ testSmartspaceDataLoaded_schedulesTimeout()
+
+ executor.runAllReady()
+ verify(timeoutCallback).invoke(eq(SMARTSPACE_KEY), eq(true))
+ }
+
+ @Test
+ fun testSmartspaceDataLoaded_alreadyExists_updatesTimeout() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ val duration = 100
+ val createTime = 1234L
+ val expireTime = createTime + duration
+ whenever(smartspaceData.headphoneConnectionTimeMillis).thenReturn(createTime)
+ whenever(smartspaceData.expiryTimeMs).thenReturn(expireTime)
+
+ mediaTimeoutListener.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+ assertThat(executor.numPending()).isEqualTo(1)
+
+ val expiryLonger = expireTime + duration
+ whenever(smartspaceData.expiryTimeMs).thenReturn(expiryLonger)
+ mediaTimeoutListener.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+
+ assertThat(executor.numPending()).isEqualTo(1)
+ assertThat(executor.advanceClockToNext()).isEqualTo(duration * 2)
+ }
+
+ @Test
+ fun testSmartspaceDataRemoved_cancelTimeout() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+
+ mediaTimeoutListener.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+ assertThat(executor.numPending()).isEqualTo(1)
+
+ mediaTimeoutListener.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
+ assertThat(executor.numPending()).isEqualTo(0)
+ }
+
+ @Test
+ fun testSmartspaceData_dozedPastTimeout_invokedOnWakeup() {
+ // Given a pending timeout
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ verify(statusBarStateController).addCallback(capture(dozingCallbackCaptor))
+ val duration = 60_000
+ val createTime = 1234L
+ val expireTime = createTime + duration
+ whenever(smartspaceData.headphoneConnectionTimeMillis).thenReturn(createTime)
+ whenever(smartspaceData.expiryTimeMs).thenReturn(expireTime)
+
+ mediaTimeoutListener.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+ assertThat(executor.numPending()).isEqualTo(1)
+
+ // And we doze past the scheduled timeout
+ val time = clock.currentTimeMillis()
+ clock.setElapsedRealtime(time + duration * 2)
+ assertThat(executor.numPending()).isEqualTo(1)
+
+ // Then when no longer dozing, the timeout runs immediately
+ dozingCallbackCaptor.value.onDozingChanged(false)
+ verify(timeoutCallback).invoke(eq(SMARTSPACE_KEY), eq(true))
+ verify(logger).logTimeout(eq(SMARTSPACE_KEY))
+
+ // and cancel any later scheduled timeout
+ assertThat(executor.numPending()).isEqualTo(0)
+ }
+
private fun loadMediaDataWithPlaybackState(state: PlaybackState) {
- `when`(mediaController.playbackState).thenReturn(state)
+ whenever(mediaController.playbackState).thenReturn(state)
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index 5e5dc8b..e201b6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -47,6 +47,7 @@
import com.android.systemui.util.time.FakeSystemClock
import javax.inject.Provider
import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Ignore
@@ -126,6 +127,7 @@
whenever(mediaControlPanelFactory.get()).thenReturn(panel)
whenever(panel.mediaViewController).thenReturn(mediaViewController)
whenever(mediaDataManager.smartspaceMediaData).thenReturn(smartspaceMediaData)
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
MediaPlayerData.clear()
}
@@ -703,4 +705,39 @@
mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
)
}
+
+ @Test
+ fun testRecommendation_persistentEnabled_newSmartspaceLoaded_updatesSort() {
+ testRecommendation_persistentEnabled_inactiveSmartspaceDataLoaded_isAdded()
+
+ // When an update to existing smartspace data is loaded
+ listener.value.onSmartspaceMediaDataLoaded(
+ SMARTSPACE_KEY,
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
+ true
+ )
+
+ // Then the carousel is updated
+ assertTrue(MediaPlayerData.playerKeys().elementAt(0).data.active)
+ assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(0).data.active)
+ }
+
+ @Test
+ fun testRecommendation_persistentEnabled_inactiveSmartspaceDataLoaded_isAdded() {
+ whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+
+ // When inactive smartspace data is loaded
+ listener.value.onSmartspaceMediaDataLoaded(
+ SMARTSPACE_KEY,
+ EMPTY_SMARTSPACE_MEDIA_DATA,
+ false
+ )
+
+ // Then it is added to the carousel with correct state
+ assertTrue(MediaPlayerData.playerKeys().elementAt(0).isSsMediaRec)
+ assertFalse(MediaPlayerData.playerKeys().elementAt(0).data.active)
+
+ assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(0).isSsMediaRec)
+ assertFalse(MediaPlayerData.visiblePlayerKeys().elementAt(0).data.active)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index ce22b19..55a33b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -52,6 +52,7 @@
import androidx.constraintlayout.widget.Barrier
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.LiveData
+import androidx.media.utils.MediaConstants
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.internal.widget.CachingIconView
@@ -206,6 +207,12 @@
@Mock private lateinit var coverContainer3: ViewGroup
@Mock private lateinit var recAppIconItem: CachingIconView
@Mock private lateinit var recCardTitle: TextView
+ @Mock private lateinit var recProgressBar1: SeekBar
+ @Mock private lateinit var recProgressBar2: SeekBar
+ @Mock private lateinit var recProgressBar3: SeekBar
+ @Mock private lateinit var recSubtitleMock1: TextView
+ @Mock private lateinit var recSubtitleMock2: TextView
+ @Mock private lateinit var recSubtitleMock3: TextView
@Mock private lateinit var coverItem: ImageView
private lateinit var coverItem1: ImageView
private lateinit var coverItem2: ImageView
@@ -906,6 +913,17 @@
}
@Test
+ fun bind_resumeState_withProgress() {
+ val progress = 0.5
+ val state = mediaData.copy(resumption = true, resumeProgress = progress)
+
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(state, PACKAGE)
+
+ verify(seekBarViewModel).updateStaticProgress(progress)
+ }
+
+ @Test
fun bindNotificationActions() {
val icon = context.getDrawable(android.R.drawable.ic_media_play)
val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
@@ -2070,6 +2088,10 @@
whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
whenever(recommendationViewHolder.mediaCoverItems)
.thenReturn(listOf(coverItem, coverItem, coverItem))
+ whenever(recommendationViewHolder.mediaProgressBars)
+ .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
+ whenever(recommendationViewHolder.mediaSubtitles)
+ .thenReturn(listOf(recSubtitleMock1, recSubtitleMock2, recSubtitleMock3))
val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bmp)
@@ -2108,6 +2130,65 @@
}
@Test
+ fun bindRecommendationWithProgressBars() {
+ fakeFeatureFlag.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, true)
+ whenever(recommendationViewHolder.mediaAppIcons)
+ .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
+ whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
+ whenever(recommendationViewHolder.mediaCoverItems)
+ .thenReturn(listOf(coverItem, coverItem, coverItem))
+ whenever(recommendationViewHolder.mediaProgressBars)
+ .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
+ whenever(recommendationViewHolder.mediaSubtitles)
+ .thenReturn(listOf(recSubtitleMock1, recSubtitleMock2, recSubtitleMock3))
+
+ val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(bmp)
+ canvas.drawColor(Color.RED)
+ val albumArt = Icon.createWithBitmap(bmp)
+ val bundle =
+ Bundle().apply {
+ putInt(
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
+ )
+ putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.5)
+ }
+ val data =
+ smartspaceData.copy(
+ recommendations =
+ listOf(
+ SmartspaceAction.Builder("id1", "title1")
+ .setSubtitle("subtitle1")
+ .setIcon(albumArt)
+ .setExtras(bundle)
+ .build(),
+ SmartspaceAction.Builder("id2", "title2")
+ .setSubtitle("subtitle1")
+ .setIcon(albumArt)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id3", "title3")
+ .setSubtitle("subtitle1")
+ .setIcon(albumArt)
+ .setExtras(Bundle.EMPTY)
+ .build()
+ )
+ )
+
+ player.attachRecommendation(recommendationViewHolder)
+ player.bindRecommendation(data)
+
+ verify(recProgressBar1).setProgress(50)
+ verify(recProgressBar1).visibility = View.VISIBLE
+ verify(recProgressBar2).visibility = View.GONE
+ verify(recProgressBar3).visibility = View.GONE
+ verify(recSubtitleMock1).visibility = View.GONE
+ verify(recSubtitleMock2).visibility = View.VISIBLE
+ verify(recSubtitleMock3).visibility = View.VISIBLE
+ }
+
+ @Test
fun onButtonClick_touchRippleFlagEnabled_playsTouchRipple() {
fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true)
val semanticActions =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 5564774..003af80f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -16,7 +16,11 @@
package com.android.systemui.media.dialog;
-import static android.media.RouteListingPreference.Item.DISABLE_REASON_SUBSCRIPTION_REQUIRED;
+import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_GO_TO_APP;
+import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_NONE;
+import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED;
+import static android.media.RouteListingPreference.Item.SUBTEXT_CUSTOM;
+import static android.media.RouteListingPreference.Item.SUBTEXT_SUBSCRIPTION_REQUIRED;
import static com.google.common.truth.Truth.assertThat;
@@ -62,6 +66,7 @@
private static final String TEST_DEVICE_ID_1 = "test_device_id_1";
private static final String TEST_DEVICE_ID_2 = "test_device_id_2";
private static final String TEST_SESSION_NAME = "test_session_name";
+ private static final String TEST_CUSTOM_SUBTEXT = "custom subtext";
private static final int TEST_MAX_VOLUME = 20;
private static final int TEST_CURRENT_VOLUME = 10;
@@ -431,12 +436,17 @@
}
@Test
- public void subStatusSupported_onBindViewHolder_bindFailedStateDevice_verifyView() {
+ public void subStatusSupported_onBindViewHolder_bindDeviceRequirePremium_verifyView() {
String deviceStatus = (String) mContext.getText(
R.string.media_output_status_require_premium);
when(mMediaOutputController.isSubStatusSupported()).thenReturn(true);
- when(mMediaDevice2.hasDisabledReason()).thenReturn(true);
- when(mMediaDevice2.getDisableReason()).thenReturn(DISABLE_REASON_SUBSCRIPTION_REQUIRED);
+ when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+ when(mMediaDevice2.hasSubtext()).thenReturn(true);
+ when(mMediaDevice2.getSubtext()).thenReturn(SUBTEXT_SUBSCRIPTION_REQUIRED);
+ when(mMediaDevice2.getSubtextString()).thenReturn(deviceStatus);
+ when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_GO_TO_APP);
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
@@ -444,9 +454,64 @@
assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mSubTitleText.getText()).isEqualTo(deviceStatus);
assertThat(mViewHolder.mTwoLineTitleText.getText()).isEqualTo(TEST_DEVICE_NAME_2);
+ assertThat(mViewHolder.mContainerLayout.hasOnClickListeners()).isTrue();
+ }
+
+ @Test
+ public void subStatusSupported_onBindViewHolder_bindDeviceWithAdPlaying_verifyView() {
+ String deviceStatus = (String) mContext.getText(
+ R.string.media_output_status_try_after_ad);
+ when(mMediaOutputController.isSubStatusSupported()).thenReturn(true);
+ when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+ when(mMediaDevice2.hasSubtext()).thenReturn(true);
+ when(mMediaDevice2.getSubtext()).thenReturn(SUBTEXT_AD_ROUTING_DISALLOWED);
+ when(mMediaDevice2.getSubtextString()).thenReturn(deviceStatus);
+ when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_NONE);
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(new LinearLayout(mContext), 0);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mSubTitleText.getText().toString()).isEqualTo(deviceStatus);
+ assertThat(mViewHolder.mTwoLineTitleText.getText().toString()).isEqualTo(
+ TEST_DEVICE_NAME_2);
+ assertThat(mViewHolder.mContainerLayout.hasOnClickListeners()).isFalse();
+ }
+
+ @Test
+ public void subStatusSupported_onBindViewHolder_bindDeviceWithOngoingSession_verifyView() {
+ when(mMediaOutputController.isSubStatusSupported()).thenReturn(true);
+ when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+ when(mMediaDevice1.hasSubtext()).thenReturn(true);
+ when(mMediaDevice1.getSubtext()).thenReturn(SUBTEXT_CUSTOM);
+ when(mMediaDevice1.getSubtextString()).thenReturn(TEST_CUSTOM_SUBTEXT);
+ when(mMediaDevice1.hasOngoingSession()).thenReturn(true);
+ when(mMediaDevice1.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_GO_TO_APP);
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(new LinearLayout(mContext), 0);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mSubTitleText.getText().toString()).isEqualTo(TEST_CUSTOM_SUBTEXT);
+ assertThat(mViewHolder.mTwoLineTitleText.getText().toString()).isEqualTo(
+ TEST_DEVICE_NAME_1);
+ assertThat(mViewHolder.mContainerLayout.hasOnClickListeners()).isTrue();
}
@Test
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 7c36e46..0bdcaf4 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
@@ -240,7 +240,6 @@
verify(mMediaController, never()).unregisterCallback(any());
}
-
@Test
public void stop_nearbyMediaDevicesManagerNotNull_unregistersNearbyDevicesCallback() {
mMediaOutputController.start(mCb);
@@ -253,7 +252,7 @@
@Test
public void tryToLaunchMediaApplication_nullIntent_skip() {
- mMediaOutputController.tryToLaunchMediaApplication();
+ mMediaOutputController.tryToLaunchMediaApplication(mDialogLaunchView);
verify(mCb, never()).dismissDialog();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index c63ca3d..db890f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -45,6 +45,7 @@
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
+import com.android.systemui.temporarydisplay.chipbar.ChipbarAnimator
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.temporarydisplay.chipbar.ChipbarLogger
import com.android.systemui.temporarydisplay.chipbar.SwipeChipbarAwayGestureHandler
@@ -145,6 +146,7 @@
configurationController,
dumpManager,
powerManager,
+ ChipbarAnimator(),
falsingManager,
falsingCollector,
swipeHandler,
@@ -622,7 +624,7 @@
}
@Test
- fun commandQueueCallback_receiverSucceededThenReceiverTriggered_invalidTransitionLogged() {
+ fun commandQueueCallback_receiverSucceededThenThisDeviceSucceeded_invalidTransitionLogged() {
displayReceiverTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
@@ -632,7 +634,7 @@
reset(windowManager)
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
routeInfo,
null
)
@@ -642,7 +644,7 @@
}
@Test
- fun commandQueueCallback_thisDeviceSucceededThenThisDeviceTriggered_invalidTransitionLogged() {
+ fun commandQueueCallback_thisDeviceSucceededThenReceiverSucceeded_invalidTransitionLogged() {
displayThisDeviceTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
@@ -652,7 +654,7 @@
reset(windowManager)
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
routeInfo,
null
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeContentTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeContentTest.java
index 9de55b1..1ddfc4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeContentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeContentTest.java
@@ -34,6 +34,22 @@
public final class SchemeContentTest extends SysuiTestCase {
@Test
+ public void testKeyColors() {
+ SchemeContent scheme = new SchemeContent(Hct.fromInt(0xff0000ff), false, 0.0);
+
+ assertThat(MaterialDynamicColors.primaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff080CFF);
+ assertThat(MaterialDynamicColors.secondaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff656DD3);
+ assertThat(MaterialDynamicColors.tertiaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff81009F);
+ assertThat(MaterialDynamicColors.neutralPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff767684);
+ assertThat(MaterialDynamicColors.neutralVariantPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff757589);
+ }
+
+ @Test
public void lightTheme_minContrast_primary() {
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, -1.0);
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xFF1218FF);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeExpressiveTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeExpressiveTest.java
index 3ef9280..31e8711 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeExpressiveTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeExpressiveTest.java
@@ -34,158 +34,174 @@
public final class SchemeExpressiveTest extends SysuiTestCase {
@Test
+ public void testKeyColors() {
+ SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 0.0);
+
+ assertThat(MaterialDynamicColors.primaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff35855F);
+ assertThat(MaterialDynamicColors.secondaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff8C6D8C);
+ assertThat(MaterialDynamicColors.tertiaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff806EA1);
+ assertThat(MaterialDynamicColors.neutralPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff79757F);
+ assertThat(MaterialDynamicColors.neutralVariantPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff7A7585);
+ }
+
+ @Test
public void lightTheme_minContrast_primary() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, -1.0);
- assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xffad603c);
+ assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff32835D);
}
@Test
public void lightTheme_standardContrast_primary() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 0.0);
- assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff924b28);
+ assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff146C48);
}
@Test
public void lightTheme_maxContrast_primary() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 1.0);
- assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff401400);
+ assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff002818);
}
@Test
public void lightTheme_minContrast_primaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, -1.0);
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
- 0xffffdbcc);
+ 0xffA2F4C6);
}
@Test
public void lightTheme_standardContrast_primaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 0.0);
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
- 0xffffdbcc);
+ 0xffA2F4C6);
}
@Test
public void lightTheme_maxContrast_primaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 1.0);
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff6f3010);
+ 0xff004D31);
}
@Test
public void lightTheme_minContrast_onPrimaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, -1.0);
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff99512e);
+ 0xff1e724e);
}
@Test
public void lightTheme_standardContrast_onPrimaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 0.0);
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff351000);
+ 0xff002112);
}
@Test
public void lightTheme_maxContrast_onPrimaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 1.0);
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
- 0xffffd0bc);
+ 0xff9aebbe);
}
@Test
public void lightTheme_minContrast_surface() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, -1.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffdf7ff);
}
@Test
public void lightTheme_standardContrast_surface() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 0.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffdf7ff);
}
@Test
public void lightTheme_maxContrast_surface() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 1.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffdf7ff);
}
@Test
public void darkTheme_minContrast_primary() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, -1.0);
- assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xffad603c);
+ assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff32835d);
}
@Test
public void darkTheme_standardContrast_primary() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 0.0);
- assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xffffb595);
+ assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff87d7ab);
}
@Test
public void darkTheme_maxContrast_primary() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 1.0);
- assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xfffff3ee);
+ assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xffd5ffe4);
}
@Test
public void darkTheme_minContrast_primaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, -1.0);
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff743413);
+ 0xff005234);
}
@Test
public void darkTheme_standardContrast_primaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 0.0);
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff743413);
+ 0xff005234);
}
@Test
public void darkTheme_maxContrast_primaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 1.0);
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
- 0xffffbb9e);
+ 0xff8bdbaf);
}
@Test
public void darkTheme_minContrast_onPrimaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, -1.0);
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
- 0xfff99f75);
+ 0xff76c59b);
}
@Test
public void darkTheme_standardContrast_onPrimaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 0.0);
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
- 0xffffdbcc);
+ 0xffa2f4c6);
}
@Test
public void darkTheme_maxContrast_onPrimaryContainer() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 1.0);
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff622706);
+ 0xff004229);
}
@Test
public void darkTheme_minContrast_surface() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, -1.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131a);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff14121a);
}
@Test
public void darkTheme_standardContrast_surface() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 0.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131a);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff14121a);
}
@Test
public void darkTheme_maxContrast_surface() {
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 1.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131a);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff14121a);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFidelityTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFidelityTest.java
index cfd0518..511f83b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFidelityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFidelityTest.java
@@ -32,6 +32,21 @@
@SmallTest
@RunWith(JUnit4.class)
public final class SchemeFidelityTest extends SysuiTestCase {
+ @Test
+ public void testKeyColors() {
+ SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 0.0);
+
+ assertThat(MaterialDynamicColors.primaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff080CFF);
+ assertThat(MaterialDynamicColors.secondaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff656DD3);
+ assertThat(MaterialDynamicColors.tertiaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff9D0002);
+ assertThat(MaterialDynamicColors.neutralPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff767684);
+ assertThat(MaterialDynamicColors.neutralVariantPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff757589);
+ }
@Test
public void lightTheme_minContrast_primary() {
@@ -251,5 +266,4 @@
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 1.0);
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12121d);
}
-
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFruitSaladTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFruitSaladTest.java
index ce6f6d9..4cf14d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFruitSaladTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFruitSaladTest.java
@@ -34,6 +34,22 @@
public final class SchemeFruitSaladTest extends SysuiTestCase {
@Test
+ public void testKeyColors() {
+ SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 0.0);
+
+ assertThat(MaterialDynamicColors.primaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff0091C0);
+ assertThat(MaterialDynamicColors.secondaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff3A7E9E);
+ assertThat(MaterialDynamicColors.tertiaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff6E72AC);
+ assertThat(MaterialDynamicColors.neutralPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff777682);
+ assertThat(MaterialDynamicColors.neutralVariantPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff75758B);
+ }
+
+ @Test
public void lightTheme_minContrast_primary() {
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, -1.0);
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff007ea7);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeMonochromeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeMonochromeTest.java
index 9ffc523..3eca4dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeMonochromeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeMonochromeTest.java
@@ -34,6 +34,22 @@
public final class SchemeMonochromeTest extends SysuiTestCase {
@Test
+ public void testKeyColors() {
+ SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, 0.0);
+
+ assertThat(MaterialDynamicColors.primaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff070707);
+ assertThat(MaterialDynamicColors.secondaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff070707);
+ assertThat(MaterialDynamicColors.tertiaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff070707);
+ assertThat(MaterialDynamicColors.neutralPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff070707);
+ assertThat(MaterialDynamicColors.neutralVariantPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff070707);
+ }
+
+ @Test
public void lightTheme_minContrast_primary() {
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, -1.0);
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff747474);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeNeutralTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeNeutralTest.java
index c6892f9..71e9f4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeNeutralTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeNeutralTest.java
@@ -34,6 +34,22 @@
public final class SchemeNeutralTest extends SysuiTestCase {
@Test
+ public void testKeyColors() {
+ SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, 0.0);
+
+ assertThat(MaterialDynamicColors.primaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff767685);
+ assertThat(MaterialDynamicColors.secondaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff777680);
+ assertThat(MaterialDynamicColors.tertiaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff75758B);
+ assertThat(MaterialDynamicColors.neutralPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff787678);
+ assertThat(MaterialDynamicColors.neutralVariantPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff787678);
+ }
+
+ @Test
public void lightTheme_minContrast_primary() {
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, -1.0);
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff737383);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeRainbowTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeRainbowTest.java
index a083622..cc6f044 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeRainbowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeRainbowTest.java
@@ -34,6 +34,22 @@
public final class SchemeRainbowTest extends SysuiTestCase {
@Test
+ public void testKeyColors() {
+ SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 0.0);
+
+ assertThat(MaterialDynamicColors.primaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff696FC4);
+ assertThat(MaterialDynamicColors.secondaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff75758B);
+ assertThat(MaterialDynamicColors.tertiaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff936B84);
+ assertThat(MaterialDynamicColors.neutralPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff070707);
+ assertThat(MaterialDynamicColors.neutralVariantPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff070707);
+ }
+
+ @Test
public void lightTheme_minContrast_primary() {
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, -1.0);
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff676DC1);
@@ -230,5 +246,4 @@
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 1.0);
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131313);
}
-
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeTonalSpotTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeTonalSpotTest.java
index b0c7f10..e4880d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeTonalSpotTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeTonalSpotTest.java
@@ -34,21 +34,37 @@
public final class SchemeTonalSpotTest extends SysuiTestCase {
@Test
+ public void testKeyColors() {
+ SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
+
+ assertThat(MaterialDynamicColors.primaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff6E72AC);
+ assertThat(MaterialDynamicColors.secondaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff75758B);
+ assertThat(MaterialDynamicColors.tertiaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff936B84);
+ assertThat(MaterialDynamicColors.neutralPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff78767A);
+ assertThat(MaterialDynamicColors.neutralVariantPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff777680);
+ }
+
+ @Test
public void lightTheme_minContrast_primary() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
- assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff6a6fb1);
+ assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff6c70aa);
}
@Test
public void lightTheme_standardContrast_primary() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
- assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff545999);
+ assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff555992);
}
@Test
public void lightTheme_maxContrast_primary() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
- assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff161a59);
+ assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff181c51);
}
@Test
@@ -69,21 +85,21 @@
public void lightTheme_maxContrast_primaryContainer() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff383c7c);
+ 0xff3a3e74);
}
@Test
public void lightTheme_minContrast_onPrimaryContainer() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff5a5fa0);
+ 0xff5C5F98);
}
@Test
public void lightTheme_standardContrast_onPrimaryContainer() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff0e1253);
+ 0xff11144B);
}
@Test
@@ -96,37 +112,37 @@
@Test
public void lightTheme_minContrast_surface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xffFCF8FD);
}
@Test
public void lightTheme_standardContrast_surface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xffFCF8FD);
}
@Test
public void lightTheme_maxContrast_surface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xffFCF8FD);
}
@Test
public void lightTheme_minContrast_onSurface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
- assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xff5f5e65);
+ assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xff605E62);
}
@Test
public void lightTheme_standardContrast_onSurface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
- assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xff1b1b21);
+ assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xff1B1B1F);
}
@Test
public void lightTheme_maxContrast_onSurface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
- assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xff1a1a20);
+ assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xff1B1A1E);
}
@Test
@@ -186,7 +202,7 @@
@Test
public void darkTheme_minContrast_primary() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
- assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff6a6fb1);
+ assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff6C70AA);
}
@Test
@@ -205,14 +221,14 @@
public void darkTheme_minContrast_primaryContainer() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff3c4180);
+ 0xff3E4278);
}
@Test
public void darkTheme_standardContrast_primaryContainer() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff3c4180);
+ 0xff3E4278);
}
@Test
@@ -226,7 +242,7 @@
public void darkTheme_minContrast_onPrimaryContainer() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
- 0xffabaff7);
+ 0xffadb0ef);
}
@Test
@@ -240,7 +256,7 @@
public void darkTheme_maxContrast_onPrimaryContainer() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
- 0xff2e3271);
+ 0xff30346A);
}
@Test
@@ -321,36 +337,36 @@
@Test
public void darkTheme_minContrast_surface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131318);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131316);
}
@Test
public void darkTheme_standardContrast_surface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131318);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131316);
}
@Test
public void darkTheme_maxContrast_surface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131318);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131316);
}
@Test
public void darkTheme_minContrast_onSurface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
- assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xffa4a2a9);
+ assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xffa4a2a6);
}
@Test
public void darkTheme_standardContrast_onSurface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
- assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xffe4e1e9);
+ assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xffe5e1e6);
}
@Test
public void darkTheme_maxContrast_onSurface() {
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
- assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xffe5e2ea);
+ assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xffe6e2e7);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeVibrantTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeVibrantTest.java
index f056a16..e5963a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeVibrantTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeVibrantTest.java
@@ -34,6 +34,22 @@
public final class SchemeVibrantTest extends SysuiTestCase {
@Test
+ public void testKeyColors() {
+ SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, 0.0);
+
+ assertThat(MaterialDynamicColors.primaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff080CFF);
+ assertThat(MaterialDynamicColors.secondaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff7B7296);
+ assertThat(MaterialDynamicColors.tertiaryPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff886C9D);
+ assertThat(MaterialDynamicColors.neutralPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff777682);
+ assertThat(MaterialDynamicColors.neutralVariantPaletteKeyColor.getArgb(scheme))
+ .isSameColorAs(0xff767685);
+ }
+
+ @Test
public void lightTheme_minContrast_primary() {
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, -1.0);
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff5660ff);
@@ -195,18 +211,18 @@
@Test
public void darkTheme_minContrast_surface() {
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, -1.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131a);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131C);
}
@Test
public void darkTheme_standardContrast_surface() {
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 0.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131a);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131C);
}
@Test
public void darkTheme_maxContrast_surface() {
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 1.0);
- assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131a);
+ assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131C);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 3da7a22..8019fb5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -42,7 +42,6 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
-import android.provider.Settings;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -158,16 +157,6 @@
}
@Test
- public void testDisableLowBatteryReminder_noNotification() {
- mGlobalSettings.putInt(Settings.Global.LOW_POWER_MODE_REMINDER_ENABLED, 0);
-
- mPowerNotificationWarnings.showLowBatteryWarning(false);
-
- verify(mMockNotificationManager, times(0))
- .notifyAsUser(anyString(), eq(SystemMessage.NOTE_POWER_LOW), any(), any());
- }
-
- @Test
public void testShowLowBatteryNotification_NotifyAsUser() {
mPowerNotificationWarnings.showLowBatteryWarning(false);
verify(mMockNotificationManager, times(1))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
index a13bece..15545a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
@@ -29,9 +29,9 @@
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.UserInfo;
import android.os.Handler;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.provider.Settings;
import android.service.dreams.IDreamManager;
import android.service.quicksettings.Tile;
@@ -123,7 +123,7 @@
// Should not be available if component is not set
mSecureSettings.putInt(Settings.Secure.SCREENSAVER_ENABLED, 1);
- when(mDreamManager.getDreamComponents()).thenReturn(null);
+ when(mDreamManager.getDreamComponentsForUser(mUserTracker.getUserId())).thenReturn(null);
mTestableLooper.processAllMessages();
assertEquals(Tile.STATE_UNAVAILABLE, mTile.getState().state);
@@ -134,9 +134,8 @@
public void testInactiveWhenDreaming() throws RemoteException {
setScreensaverEnabled(true);
- when(mDreamManager.getDreamComponents()).thenReturn(new ComponentName[]{
- COLORS_DREAM_COMPONENT_NAME
- });
+ when(mDreamManager.getDreamComponentsForUser(mUserTracker.getUserId()))
+ .thenReturn(new ComponentName[]{COLORS_DREAM_COMPONENT_NAME});
when(mDreamManager.isDreaming()).thenReturn(false);
mTile.refreshState();
@@ -148,9 +147,8 @@
public void testActive() throws RemoteException {
setScreensaverEnabled(true);
- when(mDreamManager.getDreamComponents()).thenReturn(new ComponentName[]{
- COLORS_DREAM_COMPONENT_NAME
- });
+ when(mDreamManager.getDreamComponentsForUser(mUserTracker.getUserId()))
+ .thenReturn(new ComponentName[]{COLORS_DREAM_COMPONENT_NAME});
when(mDreamManager.isDreaming()).thenReturn(true);
mTile.refreshState();
@@ -162,9 +160,8 @@
public void testClick() throws RemoteException {
// Set the AOSP dream enabled as the base setup.
setScreensaverEnabled(true);
- when(mDreamManager.getDreamComponents()).thenReturn(new ComponentName[]{
- COLORS_DREAM_COMPONENT_NAME
- });
+ when(mDreamManager.getDreamComponentsForUser(mUserTracker.getUserId()))
+ .thenReturn(new ComponentName[]{COLORS_DREAM_COMPONENT_NAME});
when(mDreamManager.isDreaming()).thenReturn(false);
mTile.refreshState();
@@ -203,21 +200,21 @@
DreamTile supportedTileAllUsers = constructTileForTest(true, false);
- UserHandle systemUserHandle = mock(UserHandle.class);
- when(systemUserHandle.isSystem()).thenReturn(true);
+ UserInfo mainUserInfo = mock(UserInfo.class);
+ when(mainUserInfo.isMain()).thenReturn(true);
- UserHandle nonSystemUserHandle = mock(UserHandle.class);
- when(nonSystemUserHandle.isSystem()).thenReturn(false);
+ UserInfo nonMainUserInfo = mock(UserInfo.class);
+ when(nonMainUserInfo.isMain()).thenReturn(false);
- when(mUserTracker.getUserHandle()).thenReturn(systemUserHandle);
+ when(mUserTracker.getUserInfo()).thenReturn(mainUserInfo);
assertTrue(supportedTileAllUsers.isAvailable());
- when(mUserTracker.getUserHandle()).thenReturn(nonSystemUserHandle);
+ when(mUserTracker.getUserInfo()).thenReturn(nonMainUserInfo);
assertTrue(supportedTileAllUsers.isAvailable());
DreamTile supportedTileOnlySystemUser = constructTileForTest(true, true);
- when(mUserTracker.getUserHandle()).thenReturn(systemUserHandle);
+ when(mUserTracker.getUserInfo()).thenReturn(mainUserInfo);
assertTrue(supportedTileOnlySystemUser.isAvailable());
- when(mUserTracker.getUserHandle()).thenReturn(nonSystemUserHandle);
+ when(mUserTracker.getUserInfo()).thenReturn(nonMainUserInfo);
assertFalse(supportedTileOnlySystemUser.isAvailable());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
index 80c39cf..addca9d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
@@ -37,10 +37,12 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.statusbar.connectivity.AccessPointController;
import com.android.systemui.statusbar.connectivity.IconState;
import com.android.systemui.statusbar.connectivity.NetworkController;
+import com.android.systemui.statusbar.connectivity.WifiIndicators;
import org.junit.Before;
import org.junit.Test;
@@ -135,4 +137,24 @@
assertThat(mTile.getState().secondaryLabel)
.isNotEqualTo(mContext.getString(R.string.status_bar_airplane));
}
+
+ @Test
+ public void setIsAirplaneMode_APM_enabled_after_wifi_disconnected() {
+ WifiIndicators wifiIndicators = new WifiIndicators(
+ /* enabled= */ true,
+ /* statusIcon= */ null,
+ /* qsIcon= */ null,
+ /* activityIn= */ false,
+ /* activityOut= */ false,
+ /* description= */ null,
+ /* isTransient= */ false,
+ /* statusLabel= */ null
+ );
+ mTile.mSignalCallback.setWifiIndicators(wifiIndicators);
+ IconState state = new IconState(true, 0, "");
+ mTile.mSignalCallback.setIsAirplaneMode(state);
+ mTestableLooper.processAllMessages();
+ assertThat(mTile.getState().icon).isEqualTo(
+ QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 541d6c2..2e73c0b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -23,7 +23,7 @@
import android.graphics.Rect
import android.hardware.HardwareBuffer
import android.os.UserHandle
-import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
@@ -35,6 +35,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
+import org.junit.Assert
import org.junit.Test
private const val USER_ID = 1
@@ -55,7 +56,7 @@
flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build()
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
val processor = RequestProcessor(imageCapture, policy, flags, scope)
var result: ScreenshotRequest? = null
@@ -78,8 +79,10 @@
fun testProcessAsync_ScreenshotData() {
flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
- val request = ScreenshotData.fromRequest(
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build())
+ val request =
+ ScreenshotData.fromRequest(
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
+ )
val processor = RequestProcessor(imageCapture, policy, flags, scope)
var result: ScreenshotData? = null
@@ -102,7 +105,7 @@
flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build()
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
val processor = RequestProcessor(imageCapture, policy, flags, scope)
val processedRequest = processor.process(request)
@@ -162,7 +165,7 @@
)
val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build()
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
val processor = RequestProcessor(imageCapture, policy, flags, scope)
val processedRequest = processor.process(request)
@@ -191,6 +194,32 @@
}
@Test
+ fun testFullScreenshot_managedProfile_nullBitmap() {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+
+ // Provide a null task bitmap when asked
+ imageCapture.image = null
+
+ // Indicate that the primary content belongs to a manged profile
+ policy.setManagedProfile(USER_ID, true)
+ policy.setDisplayContentInfo(
+ policy.getDefaultDisplayId(),
+ DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID)
+ )
+
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+ Assert.assertThrows(IllegalStateException::class.java) {
+ runBlocking { processor.process(request) }
+ }
+ Assert.assertThrows(IllegalStateException::class.java) {
+ runBlocking { processor.process(ScreenshotData.fromRequest(request)) }
+ }
+ }
+
+ @Test
fun testProvidedImageScreenshot_workProfilePolicyDisabled() = runBlocking {
flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
new file mode 100644
index 0000000..1f18d91
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
@@ -0,0 +1,161 @@
+/*
+ * 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.screenshot
+
+import android.content.ComponentName
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.IWindowManager
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ScreenshotDetectionControllerTest {
+
+ @Mock lateinit var windowManager: IWindowManager
+
+ @Mock lateinit var packageManager: PackageManager
+
+ lateinit var controller: ScreenshotDetectionController
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ controller = ScreenshotDetectionController(windowManager, packageManager)
+ }
+
+ @Test
+ fun testMaybeNotifyOfScreenshot_ignoresOverview() {
+ val data = ScreenshotData.forTesting()
+ data.source = WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW
+
+ val list = controller.maybeNotifyOfScreenshot(data)
+
+ assertTrue(list.isEmpty())
+ verify(windowManager, never()).notifyScreenshotListeners(any())
+ }
+
+ @Test
+ fun testMaybeNotifyOfScreenshot_emptySet() {
+ val data = ScreenshotData.forTesting()
+ data.source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+
+ whenever(windowManager.notifyScreenshotListeners(eq(Display.DEFAULT_DISPLAY)))
+ .thenReturn(listOf())
+
+ val list = controller.maybeNotifyOfScreenshot(data)
+
+ assertTrue(list.isEmpty())
+ }
+
+ @Test
+ fun testMaybeNotifyOfScreenshot_oneApp() {
+ val data = ScreenshotData.forTesting()
+ data.source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+
+ val component = ComponentName("package1", "class1")
+ val appName = "app name"
+ val activityInfo = mock(ActivityInfo::class.java)
+
+ whenever(
+ packageManager.getActivityInfo(
+ eq(component),
+ any(PackageManager.ComponentInfoFlags::class.java)
+ )
+ )
+ .thenReturn(activityInfo)
+ whenever(activityInfo.loadLabel(eq(packageManager))).thenReturn(appName)
+
+ whenever(windowManager.notifyScreenshotListeners(eq(Display.DEFAULT_DISPLAY)))
+ .thenReturn(listOf(component))
+
+ val list = controller.maybeNotifyOfScreenshot(data)
+
+ assertEquals(1, list.size)
+ assertEquals(appName, list[0])
+ }
+
+ @Test
+ fun testMaybeNotifyOfScreenshot_multipleApps() {
+ val data = ScreenshotData.forTesting()
+ data.source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+
+ val component1 = ComponentName("package1", "class1")
+ val component2 = ComponentName("package2", "class2")
+ val component3 = ComponentName("package3", "class3")
+ val appName1 = "app name 1"
+ val appName2 = "app name 2"
+ val appName3 = "app name 3"
+
+ val activityInfo1 = mock(ActivityInfo::class.java)
+ val activityInfo2 = mock(ActivityInfo::class.java)
+ val activityInfo3 = mock(ActivityInfo::class.java)
+
+ whenever(
+ packageManager.getActivityInfo(
+ eq(component1),
+ any(PackageManager.ComponentInfoFlags::class.java)
+ )
+ )
+ .thenReturn(activityInfo1)
+ whenever(
+ packageManager.getActivityInfo(
+ eq(component2),
+ any(PackageManager.ComponentInfoFlags::class.java)
+ )
+ )
+ .thenReturn(activityInfo2)
+ whenever(
+ packageManager.getActivityInfo(
+ eq(component3),
+ any(PackageManager.ComponentInfoFlags::class.java)
+ )
+ )
+ .thenReturn(activityInfo3)
+
+ whenever(activityInfo1.loadLabel(eq(packageManager))).thenReturn(appName1)
+ whenever(activityInfo2.loadLabel(eq(packageManager))).thenReturn(appName2)
+ whenever(activityInfo3.loadLabel(eq(packageManager))).thenReturn(appName3)
+
+ whenever(windowManager.notifyScreenshotListeners(eq(Display.DEFAULT_DISPLAY)))
+ .thenReturn(listOf(component1, component2, component3))
+
+ val list = controller.maybeNotifyOfScreenshot(data)
+
+ assertEquals(3, list.size)
+ assertEquals(appName1, list[0])
+ assertEquals(appName2, list[1])
+ assertEquals(appName3, list[2])
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 74969d0..1fa2ace 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -38,8 +38,9 @@
import com.android.internal.util.ScreenshotRequest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
import com.android.systemui.flags.Flags.SCREENSHOT_METADATA
+import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
@@ -47,6 +48,7 @@
import com.android.systemui.util.mockito.argThat
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import java.util.function.Consumer
import org.junit.Assert.assertEquals
import org.junit.Before
@@ -55,10 +57,10 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doThrow
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
private const val USER_ID = 1
private const val TASK_ID = 11
@@ -107,18 +109,20 @@
// Stub request processor as a synchronous no-op for tests with the flag enabled
doAnswer {
- val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
- val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
- consumer.accept(request)
- }.`when`(requestProcessor).processAsync(
- /* request= */ any(ScreenshotRequest::class.java), /* callback= */ any())
+ val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
+ val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
+ consumer.accept(request)
+ }
+ .whenever(requestProcessor)
+ .processAsync(/* request= */ any(ScreenshotRequest::class.java), /* callback= */ any())
doAnswer {
- val request: ScreenshotData = it.getArgument(0) as ScreenshotData
- val consumer: Consumer<ScreenshotData> = it.getArgument(1)
- consumer.accept(request)
- }.`when`(requestProcessor).processAsync(
- /* screenshot= */ any(ScreenshotData::class.java), /* callback= */ any())
+ val request: ScreenshotData = it.getArgument(0) as ScreenshotData
+ val consumer: Consumer<ScreenshotData> = it.getArgument(1)
+ consumer.accept(request)
+ }
+ .whenever(requestProcessor)
+ .processAsync(/* screenshot= */ any(ScreenshotData::class.java), /* callback= */ any())
// Flipped in selected test cases
flags.set(SCREENSHOT_WORK_PROFILE_POLICY, false)
@@ -162,37 +166,52 @@
/* requestCallback = */ any()
)
- assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
+ assertEquals("Expected one UiEvent", 1, eventLogger.numLogs())
val logEvent = eventLogger.get(0)
- assertEquals("Expected SCREENSHOT_REQUESTED UiEvent",
- logEvent.eventId, SCREENSHOT_REQUESTED_KEY_OTHER.id)
- assertEquals("Expected supplied package name",
- topComponent.packageName, eventLogger.get(0).packageName)
+ assertEquals(
+ "Expected SCREENSHOT_REQUESTED UiEvent",
+ logEvent.eventId,
+ SCREENSHOT_REQUESTED_KEY_OTHER.id
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ eventLogger.get(0).packageName
+ )
}
@Test
fun takeScreenshotFullscreen_screenshotDataEnabled() {
flags.set(SCREENSHOT_METADATA, true)
- val request = ScreenshotRequest.Builder(
- TAKE_SCREENSHOT_FULLSCREEN,
- SCREENSHOT_KEY_OTHER).setTopComponent(topComponent).build()
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
+ .setTopComponent(topComponent)
+ .build()
- service.handleRequest(request, { /* onSaved */ }, callback)
+ service.handleRequest(request, { /* onSaved */}, callback)
- verify(controller, times(1)).handleScreenshot(
- eq(ScreenshotData.fromRequest(request)),
- /* onSavedListener = */ any(),
- /* requestCallback = */ any())
+ verify(controller, times(1))
+ .handleScreenshot(
+ eq(ScreenshotData.fromRequest(request)),
+ /* onSavedListener = */ any(),
+ /* requestCallback = */ any()
+ )
assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
val logEvent = eventLogger.get(0)
- assertEquals("Expected SCREENSHOT_REQUESTED UiEvent",
- logEvent.eventId, SCREENSHOT_REQUESTED_KEY_OTHER.id)
- assertEquals("Expected supplied package name",
- topComponent.packageName, eventLogger.get(0).packageName)
+ assertEquals(
+ "Expected SCREENSHOT_REQUESTED UiEvent",
+ logEvent.eventId,
+ SCREENSHOT_REQUESTED_KEY_OTHER.id
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ eventLogger.get(0).packageName
+ )
}
@Test
@@ -224,7 +243,7 @@
/* requestCallback = */ any()
)
- assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
+ assertEquals("Expected one UiEvent", 1, eventLogger.numLogs())
val logEvent = eventLogger.get(0)
assertEquals(
@@ -241,6 +260,8 @@
@Test
fun takeScreenshotFullscreen_userLocked() {
+ flags.set(SCREENSHOT_METADATA, true)
+
whenever(userManager.isUserUnlocked).thenReturn(false)
val request =
@@ -253,10 +274,36 @@
verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
verify(callback, times(1)).reportError()
verifyZeroInteractions(controller)
+
+ assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
+ val requestEvent = eventLogger.get(0)
+ assertEquals(
+ "Expected SCREENSHOT_REQUESTED_* UiEvent",
+ SCREENSHOT_REQUESTED_KEY_OTHER.id,
+ requestEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ requestEvent.packageName
+ )
+ val failureEvent = eventLogger.get(1)
+ assertEquals(
+ "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
+ SCREENSHOT_CAPTURE_FAILED.id,
+ failureEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ failureEvent.packageName
+ )
}
@Test
fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() {
+ flags.set(SCREENSHOT_METADATA, true)
+
whenever(devicePolicyManager.getScreenCaptureDisabled(isNull(), eq(UserHandle.USER_ALL)))
.thenReturn(true)
@@ -279,6 +326,206 @@
// error shown: Toast.makeText(...).show(), untestable
verify(callback, times(1)).reportError()
verifyZeroInteractions(controller)
+ assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
+ val requestEvent = eventLogger.get(0)
+ assertEquals(
+ "Expected SCREENSHOT_REQUESTED_* UiEvent",
+ SCREENSHOT_REQUESTED_KEY_OTHER.id,
+ requestEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ requestEvent.packageName
+ )
+ val failureEvent = eventLogger.get(1)
+ assertEquals(
+ "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
+ SCREENSHOT_CAPTURE_FAILED.id,
+ failureEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ failureEvent.packageName
+ )
+ }
+
+ @Test
+ fun takeScreenshotFullscreen_userLocked_metadataDisabled() {
+ flags.set(SCREENSHOT_METADATA, false)
+ whenever(userManager.isUserUnlocked).thenReturn(false)
+
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
+ .setTopComponent(topComponent)
+ .build()
+
+ service.handleRequest(request, { /* onSaved */}, callback)
+
+ verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
+ verify(callback, times(1)).reportError()
+ verifyZeroInteractions(controller)
+
+ assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
+ val requestEvent = eventLogger.get(0)
+ assertEquals(
+ "Expected SCREENSHOT_REQUESTED_* UiEvent",
+ SCREENSHOT_REQUESTED_KEY_OTHER.id,
+ requestEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ requestEvent.packageName
+ )
+ val failureEvent = eventLogger.get(1)
+ assertEquals(
+ "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
+ SCREENSHOT_CAPTURE_FAILED.id,
+ failureEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ failureEvent.packageName
+ )
+ }
+
+ @Test
+ fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers_metadataDisabled() {
+ flags.set(SCREENSHOT_METADATA, false)
+
+ whenever(devicePolicyManager.getScreenCaptureDisabled(isNull(), eq(UserHandle.USER_ALL)))
+ .thenReturn(true)
+
+ whenever(
+ devicePolicyResourcesManager.getString(
+ eq(SCREENSHOT_BLOCKED_BY_ADMIN),
+ /* Supplier<String> */
+ any(),
+ )
+ )
+ .thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
+
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
+ .setTopComponent(topComponent)
+ .build()
+
+ service.handleRequest(request, { /* onSaved */}, callback)
+
+ // error shown: Toast.makeText(...).show(), untestable
+ verify(callback, times(1)).reportError()
+ verifyZeroInteractions(controller)
+ assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
+ val requestEvent = eventLogger.get(0)
+ assertEquals(
+ "Expected SCREENSHOT_REQUESTED_* UiEvent",
+ SCREENSHOT_REQUESTED_KEY_OTHER.id,
+ requestEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ requestEvent.packageName
+ )
+ val failureEvent = eventLogger.get(1)
+ assertEquals(
+ "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
+ SCREENSHOT_CAPTURE_FAILED.id,
+ failureEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ failureEvent.packageName
+ )
+ }
+
+ @Test
+ fun takeScreenshot_workProfile_nullBitmap_metadataDisabled() {
+ flags.set(SCREENSHOT_METADATA, false)
+
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
+ .setTopComponent(topComponent)
+ .build()
+
+ doThrow(IllegalStateException::class.java)
+ .whenever(requestProcessor)
+ .processAsync(any(ScreenshotRequest::class.java), any())
+
+ service.handleRequest(request, { /* onSaved */}, callback)
+
+ verify(callback, times(1)).reportError()
+ verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
+ verifyZeroInteractions(controller)
+ assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
+ val requestEvent = eventLogger.get(0)
+ assertEquals(
+ "Expected SCREENSHOT_REQUESTED_* UiEvent",
+ SCREENSHOT_REQUESTED_KEY_OTHER.id,
+ requestEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ requestEvent.packageName
+ )
+ val failureEvent = eventLogger.get(1)
+ assertEquals(
+ "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
+ SCREENSHOT_CAPTURE_FAILED.id,
+ failureEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ failureEvent.packageName
+ )
+ }
+ @Test
+ fun takeScreenshot_workProfile_nullBitmap() {
+ flags.set(SCREENSHOT_METADATA, true)
+
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
+ .setTopComponent(topComponent)
+ .build()
+
+ doThrow(IllegalStateException::class.java)
+ .whenever(requestProcessor)
+ .processAsync(any(ScreenshotData::class.java), any())
+
+ service.handleRequest(request, { /* onSaved */}, callback)
+
+ verify(callback, times(1)).reportError()
+ verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
+ verifyZeroInteractions(controller)
+ assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
+ val requestEvent = eventLogger.get(0)
+ assertEquals(
+ "Expected SCREENSHOT_REQUESTED_* UiEvent",
+ SCREENSHOT_REQUESTED_KEY_OTHER.id,
+ requestEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ requestEvent.packageName
+ )
+ val failureEvent = eventLogger.get(1)
+ assertEquals(
+ "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
+ SCREENSHOT_CAPTURE_FAILED.id,
+ failureEvent.eventId
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ failureEvent.packageName
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
index 576652f..3440f91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
@@ -59,9 +59,7 @@
@RunWith(AndroidTestingRunner.class)
public class WorkProfileMessageControllerTest extends SysuiTestCase {
private static final String DEFAULT_LABEL = "default label";
- private static final String BADGED_DEFAULT_LABEL = "badged default label";
private static final String APP_LABEL = "app label";
- private static final String BADGED_APP_LABEL = "badged app label";
private static final UserHandle NON_WORK_USER = UserHandle.of(0);
private static final UserHandle WORK_USER = UserHandle.of(10);
@@ -91,10 +89,6 @@
eq(WorkProfileMessageController.SHARED_PREFERENCES_NAME),
eq(Context.MODE_PRIVATE))).thenReturn(mSharedPreferences);
when(mMockContext.getString(ArgumentMatchers.anyInt())).thenReturn(DEFAULT_LABEL);
- when(mPackageManager.getUserBadgedLabel(eq(DEFAULT_LABEL), any()))
- .thenReturn(BADGED_DEFAULT_LABEL);
- when(mPackageManager.getUserBadgedLabel(eq(APP_LABEL), any()))
- .thenReturn(BADGED_APP_LABEL);
when(mPackageManager.getActivityIcon(any(ComponentName.class)))
.thenReturn(mActivityIcon);
when(mPackageManager.getUserBadgedIcon(
@@ -133,7 +127,7 @@
WorkProfileMessageController.WorkProfileFirstRunData data =
mMessageController.onScreenshotTaken(WORK_USER);
- assertEquals(BADGED_DEFAULT_LABEL, data.getAppName());
+ assertEquals(DEFAULT_LABEL, data.getAppName());
assertNull(data.getIcon());
}
@@ -142,7 +136,7 @@
WorkProfileMessageController.WorkProfileFirstRunData data =
mMessageController.onScreenshotTaken(WORK_USER);
- assertEquals(BADGED_APP_LABEL, data.getAppName());
+ assertEquals(APP_LABEL, data.getAppName());
assertEquals(mBadgedActivityIcon, data.getIcon());
}
@@ -151,7 +145,7 @@
ViewGroup layout = (ViewGroup) LayoutInflater.from(mContext).inflate(
R.layout.screenshot_work_profile_first_run, null);
WorkProfileMessageController.WorkProfileFirstRunData data =
- new WorkProfileMessageController.WorkProfileFirstRunData(BADGED_APP_LABEL,
+ new WorkProfileMessageController.WorkProfileFirstRunData(APP_LABEL,
mBadgedActivityIcon);
final CountDownLatch countdown = new CountDownLatch(1);
mMessageController.populateView(layout, data, () -> {
@@ -163,7 +157,7 @@
assertEquals(mBadgedActivityIcon, image.getDrawable());
TextView text = layout.findViewById(R.id.screenshot_message_content);
// The app name is used in a template, but at least validate that it was inserted.
- assertTrue(text.getText().toString().contains(BADGED_APP_LABEL));
+ assertTrue(text.getText().toString().contains(APP_LABEL));
// Validate that clicking the dismiss button calls back properly.
assertEquals(1, countdown.getCount());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperServiceTest.java
new file mode 100644
index 0000000..6e8f5fe
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperServiceTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.appclips;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.graphics.ColorSpace;
+import android.hardware.HardwareBuffer;
+import android.os.RemoteException;
+import android.view.Display;
+import android.window.ScreenCapture.ScreenshotHardwareBuffer;
+import android.window.ScreenCapture.ScreenshotSync;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.wm.shell.bubbles.Bubbles;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+@RunWith(AndroidJUnit4.class)
+public final class AppClipsScreenshotHelperServiceTest extends SysuiTestCase {
+
+ private static final Intent FAKE_INTENT = new Intent();
+ private static final int DEFAULT_DISPLAY = Display.DEFAULT_DISPLAY;
+ private static final HardwareBuffer FAKE_HARDWARE_BUFFER =
+ HardwareBuffer.create(1, 1, HardwareBuffer.RGBA_8888, 1,
+ HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
+ private static final ColorSpace FAKE_COLOR_SPACE = ColorSpace.get(ColorSpace.Named.SRGB);
+ private static final ScreenshotHardwareBufferInternal EXPECTED_SCREENSHOT_BUFFER =
+ new ScreenshotHardwareBufferInternal(
+ new ScreenshotHardwareBuffer(FAKE_HARDWARE_BUFFER, FAKE_COLOR_SPACE, false,
+ false));
+
+ @Mock private Optional<Bubbles> mBubblesOptional;
+ @Mock private Bubbles mBubbles;
+ @Mock private ScreenshotHardwareBuffer mScreenshotHardwareBuffer;
+ @Mock private ScreenshotSync mScreenshotSync;
+
+ private AppClipsScreenshotHelperService mAppClipsScreenshotHelperService;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mAppClipsScreenshotHelperService = new AppClipsScreenshotHelperService(mBubblesOptional);
+ }
+
+ @Test
+ public void emptyBubbles_shouldReturnNull() throws RemoteException {
+ when(mBubblesOptional.isEmpty()).thenReturn(true);
+
+ assertThat(getInterface().takeScreenshot(DEFAULT_DISPLAY)).isNull();
+ }
+
+ @Test
+ public void bubblesPresent_screenshotFailed_ShouldReturnNull() throws RemoteException {
+ when(mBubblesOptional.isEmpty()).thenReturn(false);
+ when(mBubblesOptional.get()).thenReturn(mBubbles);
+ when(mBubbles.getScreenshotExcludingBubble(DEFAULT_DISPLAY)).thenReturn(mScreenshotSync);
+ when(mScreenshotSync.get()).thenReturn(null);
+
+ assertThat(getInterface().takeScreenshot(DEFAULT_DISPLAY)).isNull();
+ }
+
+ @Test
+ public void bubblesPresent_screenshotSuccess_shouldReturnScreenshot() throws RemoteException {
+ when(mBubblesOptional.isEmpty()).thenReturn(false);
+ when(mBubblesOptional.get()).thenReturn(mBubbles);
+ when(mBubbles.getScreenshotExcludingBubble(DEFAULT_DISPLAY)).thenReturn(mScreenshotSync);
+ when(mScreenshotSync.get()).thenReturn(mScreenshotHardwareBuffer);
+ when(mScreenshotHardwareBuffer.getHardwareBuffer()).thenReturn(FAKE_HARDWARE_BUFFER);
+ when(mScreenshotHardwareBuffer.getColorSpace()).thenReturn(FAKE_COLOR_SPACE);
+
+ assertThat(getInterface().takeScreenshot(DEFAULT_DISPLAY)).isEqualTo(
+ EXPECTED_SCREENSHOT_BUFFER);
+ }
+
+ private IAppClipsScreenshotHelperService getInterface() {
+ return IAppClipsScreenshotHelperService.Stub.asInterface(
+ mAppClipsScreenshotHelperService.onBind(FAKE_INTENT));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
new file mode 100644
index 0000000..b55fe36
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.screenshot.appclips;
+
+import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.statusbar.IAppClipsService;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.wm.shell.bubbles.Bubbles;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+@RunWith(AndroidJUnit4.class)
+public final class AppClipsServiceTest extends SysuiTestCase {
+
+ private static final Intent FAKE_INTENT = new Intent();
+ private static final int FAKE_TASK_ID = 42;
+ private static final String EMPTY = "";
+
+ @Mock @Application private Context mMockContext;
+ @Mock private FeatureFlags mFeatureFlags;
+ @Mock private Optional<Bubbles> mOptionalBubbles;
+ @Mock private Bubbles mBubbles;
+ @Mock private DevicePolicyManager mDevicePolicyManager;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void flagOff_shouldReturnFalse() throws RemoteException {
+ when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(false);
+
+ assertThat(getInterfaceWithRealContext()
+ .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
+ }
+
+ @Test
+ public void emptyBubbles_shouldReturnFalse() throws RemoteException {
+ when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+ when(mOptionalBubbles.isEmpty()).thenReturn(true);
+
+ assertThat(getInterfaceWithRealContext()
+ .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
+ }
+
+ @Test
+ public void taskIdNotAppBubble_shouldReturnFalse() throws RemoteException {
+ when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+ when(mOptionalBubbles.isEmpty()).thenReturn(false);
+ when(mOptionalBubbles.get()).thenReturn(mBubbles);
+ when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(false);
+
+ assertThat(getInterfaceWithRealContext()
+ .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
+ }
+
+ @Test
+ public void dpmScreenshotBlocked_shouldReturnFalse() throws RemoteException {
+ when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+ when(mOptionalBubbles.isEmpty()).thenReturn(false);
+ when(mOptionalBubbles.get()).thenReturn(mBubbles);
+ when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
+ when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(true);
+
+ assertThat(getInterfaceWithRealContext()
+ .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
+ }
+
+ @Test
+ public void configComponentNameNotValid_shouldReturnFalse() throws RemoteException {
+ when(mMockContext.getString(anyInt())).thenReturn(EMPTY);
+ when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+ when(mOptionalBubbles.isEmpty()).thenReturn(false);
+ when(mOptionalBubbles.get()).thenReturn(mBubbles);
+ when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
+ when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
+
+ assertThat(getInterfaceWithMockContext()
+ .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
+ }
+
+ @Test
+ public void allPrerequisitesSatisfy_shouldReturnTrue() throws RemoteException {
+ when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+ when(mOptionalBubbles.isEmpty()).thenReturn(false);
+ when(mOptionalBubbles.get()).thenReturn(mBubbles);
+ when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
+ when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
+
+ assertThat(getInterfaceWithRealContext()
+ .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isTrue();
+ }
+
+ private IAppClipsService getInterfaceWithRealContext() {
+ AppClipsService appClipsService = new AppClipsService(getContext(), mFeatureFlags,
+ mOptionalBubbles, mDevicePolicyManager);
+ return getInterfaceFromService(appClipsService);
+ }
+
+ private IAppClipsService getInterfaceWithMockContext() {
+ AppClipsService appClipsService = new AppClipsService(mMockContext, mFeatureFlags,
+ mOptionalBubbles, mDevicePolicyManager);
+ return getInterfaceFromService(appClipsService);
+ }
+
+ private static IAppClipsService getInterfaceFromService(AppClipsService appClipsService) {
+ IBinder iBinder = appClipsService.onBind(FAKE_INTENT);
+ return IAppClipsService.Stub.asInterface(iBinder);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
new file mode 100644
index 0000000..295d127
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
@@ -0,0 +1,269 @@
+/*
+ * 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.screenshot.appclips;
+
+import static android.app.Instrumentation.ActivityResult;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
+import static android.content.Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE;
+
+import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
+import static com.android.systemui.screenshot.AppClipsTrampolineActivity.ACTION_FINISH_FROM_TRAMPOLINE;
+import static com.android.systemui.screenshot.AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI;
+import static com.android.systemui.screenshot.AppClipsTrampolineActivity.PERMISSION_SELF;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.intercepting.SingleActivityFactory;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.notetask.NoteTaskController;
+import com.android.systemui.screenshot.AppClipsTrampolineActivity;
+import com.android.wm.shell.bubbles.Bubbles;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+@RunWith(AndroidTestingRunner.class)
+public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
+
+ private static final String TEST_URI_STRING = "www.test-uri.com";
+ private static final Uri TEST_URI = Uri.parse(TEST_URI_STRING);
+ private static final int TIME_OUT = 5000;
+
+ @Mock
+ private DevicePolicyManager mDevicePolicyManager;
+ @Mock
+ private FeatureFlags mFeatureFlags;
+ @Mock
+ private Optional<Bubbles> mOptionalBubbles;
+ @Mock
+ private Bubbles mBubbles;
+ @Mock
+ private NoteTaskController mNoteTaskController;
+ @Main
+ private Handler mMainHandler;
+
+ // Using the deprecated ActivityTestRule and SingleActivityFactory to help with injecting mocks
+ // and getting result from activity both of which are difficult to do in newer APIs.
+ private final SingleActivityFactory<AppClipsTrampolineActivityTestable> mFactory =
+ new SingleActivityFactory<>(AppClipsTrampolineActivityTestable.class) {
+ @Override
+ protected AppClipsTrampolineActivityTestable create(Intent unUsed) {
+ return new AppClipsTrampolineActivityTestable(mDevicePolicyManager,
+ mFeatureFlags, mOptionalBubbles, mNoteTaskController, mMainHandler);
+ }
+ };
+
+ @Rule
+ public final ActivityTestRule<AppClipsTrampolineActivityTestable> mActivityRule =
+ new ActivityTestRule<>(mFactory, false, false);
+
+ private Context mContext;
+ private Intent mActivityIntent;
+ private ComponentName mExpectedComponentName;
+ private Intent mKillAppClipsActivityBroadcast;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = getContext();
+ mMainHandler = mContext.getMainThreadHandler();
+
+ mActivityIntent = new Intent(mContext, AppClipsTrampolineActivityTestable.class);
+ mExpectedComponentName = ComponentName.unflattenFromString(
+ mContext.getString(
+ R.string.config_screenshotAppClipsActivityComponent));
+ mKillAppClipsActivityBroadcast = new Intent(ACTION_FINISH_FROM_TRAMPOLINE)
+ .setComponent(mExpectedComponentName)
+ .setPackage(mExpectedComponentName.getPackageName());
+ }
+
+ @After
+ public void tearDown() {
+ mContext.sendBroadcast(mKillAppClipsActivityBroadcast, PERMISSION_SELF);
+ mActivityRule.finishActivity();
+ }
+
+ @Test
+ public void configComponentName_shouldResolve() {
+ // Verify component name is setup - has package and class name.
+ assertThat(mExpectedComponentName).isNotNull();
+ assertThat(mExpectedComponentName.getPackageName()).isNotEmpty();
+ assertThat(mExpectedComponentName.getClassName()).isNotEmpty();
+
+ // Verify an intent when launched with above component resolves to the same component to
+ // confirm that component from above is available in framework.
+ Intent appClipsActivityIntent = new Intent();
+ appClipsActivityIntent.setComponent(mExpectedComponentName);
+ ResolveInfo resolveInfo = getContext().getPackageManager().resolveActivity(
+ appClipsActivityIntent, PackageManager.ResolveInfoFlags.of(0));
+ ActivityInfo activityInfo = resolveInfo.activityInfo;
+
+ assertThat(activityInfo.packageName).isEqualTo(
+ mExpectedComponentName.getPackageName());
+ assertThat(activityInfo.name).isEqualTo(mExpectedComponentName.getClassName());
+ }
+
+ @Test
+ public void flagOff_shouldFinishWithResultCancel() {
+ when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(false);
+
+ mActivityRule.launchActivity(mActivityIntent);
+
+ assertThat(mActivityRule.getActivityResult().getResultCode())
+ .isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ public void bubblesEmpty_shouldFinishWithFailed() {
+ when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+ when(mOptionalBubbles.isEmpty()).thenReturn(true);
+
+ mActivityRule.launchActivity(mActivityIntent);
+
+ ActivityResult actualResult = mActivityRule.getActivityResult();
+ assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
+ assertThat(getStatusCodeExtra(actualResult.getResultData()))
+ .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ }
+
+ @Test
+ public void taskIdNotAppBubble_shouldFinishWithWindowModeUnsupported() {
+ when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+ when(mOptionalBubbles.isEmpty()).thenReturn(false);
+ when(mOptionalBubbles.get()).thenReturn(mBubbles);
+ when(mBubbles.isAppBubbleTaskId(anyInt())).thenReturn(false);
+
+ mActivityRule.launchActivity(mActivityIntent);
+
+ ActivityResult actualResult = mActivityRule.getActivityResult();
+ assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
+ assertThat(getStatusCodeExtra(actualResult.getResultData()))
+ .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED);
+ }
+
+ @Test
+ public void dpmScreenshotBlocked_shouldFinishWithBlockedByAdmin() {
+ when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+ when(mOptionalBubbles.isEmpty()).thenReturn(false);
+ when(mOptionalBubbles.get()).thenReturn(mBubbles);
+ when(mBubbles.isAppBubbleTaskId(anyInt())).thenReturn(true);
+ when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(true);
+
+ mActivityRule.launchActivity(mActivityIntent);
+
+ ActivityResult actualResult = mActivityRule.getActivityResult();
+ assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
+ assertThat(getStatusCodeExtra(actualResult.getResultData()))
+ .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN);
+ }
+
+ @Test
+ public void startAppClipsActivity_userCanceled_shouldReturnUserCanceled() {
+ mockToSatisfyAllPrerequisites();
+
+ AppClipsTrampolineActivityTestable activity = mActivityRule.launchActivity(mActivityIntent);
+ waitForIdleSync();
+
+ Bundle bundle = new Bundle();
+ bundle.putInt(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE,
+ CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED);
+ activity.getResultReceiverForTest().send(Activity.RESULT_OK, bundle);
+ waitForIdleSync();
+
+ ActivityResult actualResult = mActivityRule.getActivityResult();
+ assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
+ assertThat(getStatusCodeExtra(actualResult.getResultData()))
+ .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED);
+ }
+
+ @Test
+ public void startAppClipsActivity_shouldReturnSuccess() {
+ mockToSatisfyAllPrerequisites();
+
+ AppClipsTrampolineActivityTestable activity = mActivityRule.launchActivity(mActivityIntent);
+ waitForIdleSync();
+
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(EXTRA_SCREENSHOT_URI, TEST_URI);
+ bundle.putInt(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, CAPTURE_CONTENT_FOR_NOTE_SUCCESS);
+ activity.getResultReceiverForTest().send(Activity.RESULT_OK, bundle);
+ waitForIdleSync();
+
+ ActivityResult actualResult = mActivityRule.getActivityResult();
+ assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
+ assertThat(getStatusCodeExtra(actualResult.getResultData()))
+ .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_SUCCESS);
+ assertThat(actualResult.getResultData().getData()).isEqualTo(TEST_URI);
+ }
+
+ private void mockToSatisfyAllPrerequisites() {
+ when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+ when(mOptionalBubbles.isEmpty()).thenReturn(false);
+ when(mOptionalBubbles.get()).thenReturn(mBubbles);
+ when(mBubbles.isAppBubbleTaskId(anyInt())).thenReturn(true);
+ when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
+ }
+
+ public static final class AppClipsTrampolineActivityTestable extends
+ AppClipsTrampolineActivity {
+
+ public AppClipsTrampolineActivityTestable(DevicePolicyManager devicePolicyManager,
+ FeatureFlags flags,
+ Optional<Bubbles> optionalBubbles,
+ NoteTaskController noteTaskController,
+ @Main Handler mainHandler) {
+ super(devicePolicyManager, flags, optionalBubbles, noteTaskController, mainHandler);
+ }
+ }
+
+ private static int getStatusCodeExtra(Intent intent) {
+ return intent.getIntExtra(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, -100);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
new file mode 100644
index 0000000..d5af7ce1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
@@ -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.
+ */
+
+package com.android.systemui.screenshot;
+
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.net.Uri;
+import android.os.UserHandle;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.screenshot.appclips.AppClipsCrossProcessHelper;
+
+import com.google.common.util.concurrent.Futures;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.time.ZonedDateTime;
+import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidJUnit4.class)
+public final class AppClipsViewModelTest extends SysuiTestCase {
+
+ private static final Bitmap FAKE_BITMAP = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
+ private static final Drawable FAKE_DRAWABLE = new ShapeDrawable();
+ private static final Rect FAKE_RECT = new Rect();
+ private static final Uri FAKE_URI = Uri.parse("www.test-uri.com");
+
+ @Mock private AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
+ @Mock private ImageExporter mImageExporter;
+
+ private com.android.systemui.screenshot.AppClipsViewModel mViewModel;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mViewModel = new AppClipsViewModel.Factory(mAppClipsCrossProcessHelper, mImageExporter,
+ getContext().getMainExecutor(), directExecutor()).create(AppClipsViewModel.class);
+ }
+
+ @Test
+ public void performScreenshot_fails_shouldUpdateErrorWithFailed() {
+ when(mAppClipsCrossProcessHelper.takeScreenshot()).thenReturn(null);
+
+ mViewModel.performScreenshot();
+ waitForIdleSync();
+
+ verify(mAppClipsCrossProcessHelper).takeScreenshot();
+ assertThat(mViewModel.getErrorLiveData().getValue())
+ .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ assertThat(mViewModel.getResultLiveData().getValue()).isNull();
+ }
+
+ @Test
+ public void performScreenshot_succeeds_shouldUpdateScreenshotWithBitmap() {
+ when(mAppClipsCrossProcessHelper.takeScreenshot()).thenReturn(FAKE_BITMAP);
+
+ mViewModel.performScreenshot();
+ waitForIdleSync();
+
+ verify(mAppClipsCrossProcessHelper).takeScreenshot();
+ assertThat(mViewModel.getErrorLiveData().getValue()).isNull();
+ assertThat(mViewModel.getScreenshot().getValue()).isEqualTo(FAKE_BITMAP);
+ }
+
+ @Test
+ public void saveScreenshot_throwsError_shouldUpdateErrorWithFailed() {
+ when(mImageExporter.export(any(Executor.class), any(UUID.class), eq(null), any(
+ ZonedDateTime.class), any(UserHandle.class))).thenReturn(
+ Futures.immediateFailedFuture(new ExecutionException(new Throwable())));
+
+ mViewModel.saveScreenshotThenFinish(FAKE_DRAWABLE, FAKE_RECT);
+ waitForIdleSync();
+
+ assertThat(mViewModel.getErrorLiveData().getValue())
+ .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ assertThat(mViewModel.getResultLiveData().getValue()).isNull();
+ }
+
+ @Test
+ public void saveScreenshot_failsSilently_shouldUpdateErrorWithFailed() {
+ when(mImageExporter.export(any(Executor.class), any(UUID.class), eq(null), any(
+ ZonedDateTime.class), any(UserHandle.class))).thenReturn(
+ Futures.immediateFuture(new ImageExporter.Result()));
+
+ mViewModel.saveScreenshotThenFinish(FAKE_DRAWABLE, FAKE_RECT);
+ waitForIdleSync();
+
+ assertThat(mViewModel.getErrorLiveData().getValue())
+ .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ assertThat(mViewModel.getResultLiveData().getValue()).isNull();
+ }
+
+ @Test
+ public void saveScreenshot_succeeds_shouldUpdateResultWithUri() {
+ ImageExporter.Result result = new ImageExporter.Result();
+ result.uri = FAKE_URI;
+ when(mImageExporter.export(any(Executor.class), any(UUID.class), eq(null), any(
+ ZonedDateTime.class), any(UserHandle.class))).thenReturn(
+ Futures.immediateFuture(result));
+
+ mViewModel.saveScreenshotThenFinish(FAKE_DRAWABLE, FAKE_RECT);
+ waitForIdleSync();
+
+ assertThat(mViewModel.getErrorLiveData().getValue()).isNull();
+ assertThat(mViewModel.getResultLiveData().getValue()).isEqualTo(FAKE_URI);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 4d7741ad..78bebb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProviderPlugin
+import com.android.systemui.plugins.ClockSettings
import com.android.systemui.plugins.PluginListener
import com.android.systemui.plugins.PluginManager
import com.android.systemui.util.mockito.argumentCaptor
@@ -59,7 +60,7 @@
private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
private lateinit var registry: ClockRegistry
- private var settingValue: String = ""
+ private var settingValue: ClockSettings? = null
companion object {
private fun failFactory(): ClockController {
@@ -79,7 +80,8 @@
private val thumbnailCallbacks = mutableMapOf<ClockId, () -> Drawable?>()
override fun getClocks() = metadata
- override fun createClock(id: ClockId): ClockController = createCallbacks[id]!!()
+ override fun createClock(settings: ClockSettings): ClockController =
+ createCallbacks[settings.clockId!!]!!()
override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!()
fun addClock(
@@ -110,7 +112,7 @@
userHandle = UserHandle.USER_ALL,
defaultClockProvider = fakeDefaultProvider
) {
- override var currentClockId: ClockId
+ override var settings: ClockSettings?
get() = settingValue
set(value) { settingValue = value }
}
@@ -185,7 +187,7 @@
.addClock("clock_1", "clock 1")
.addClock("clock_2", "clock 2")
- settingValue = "clock_3"
+ settingValue = ClockSettings("clock_3", null, null)
val plugin2 = FakeClockPlugin()
.addClock("clock_3", "clock 3", { mockClock })
.addClock("clock_4", "clock 4")
@@ -203,7 +205,7 @@
.addClock("clock_1", "clock 1")
.addClock("clock_2", "clock 2")
- settingValue = "clock_3"
+ settingValue = ClockSettings("clock_3", null, null)
val plugin2 = FakeClockPlugin()
.addClock("clock_3", "clock 3")
.addClock("clock_4", "clock 4")
@@ -222,7 +224,7 @@
.addClock("clock_1", "clock 1")
.addClock("clock_2", "clock 2")
- settingValue = "clock_3"
+ settingValue = ClockSettings("clock_3", null, null)
val plugin2 = FakeClockPlugin()
.addClock("clock_3", "clock 3", { mockClock })
.addClock("clock_4", "clock 4")
@@ -242,8 +244,8 @@
@Test
fun jsonDeserialization_gotExpectedObject() {
- val expected = ClockRegistry.ClockSetting("ID", 500)
- val actual = ClockRegistry.ClockSetting.deserialize("""{
+ val expected = ClockSettings("ID", null, 500)
+ val actual = ClockSettings.deserialize("""{
"clockId":"ID",
"_applied_timestamp":500
}""")
@@ -252,15 +254,15 @@
@Test
fun jsonDeserialization_noTimestamp_gotExpectedObject() {
- val expected = ClockRegistry.ClockSetting("ID", null)
- val actual = ClockRegistry.ClockSetting.deserialize("{\"clockId\":\"ID\"}")
+ val expected = ClockSettings("ID", null, null)
+ val actual = ClockSettings.deserialize("{\"clockId\":\"ID\"}")
assertEquals(expected, actual)
}
@Test
fun jsonDeserialization_nullTimestamp_gotExpectedObject() {
- val expected = ClockRegistry.ClockSetting("ID", null)
- val actual = ClockRegistry.ClockSetting.deserialize("""{
+ val expected = ClockSettings("ID", null, null)
+ val actual = ClockSettings.deserialize("""{
"clockId":"ID",
"_applied_timestamp":null
}""")
@@ -269,22 +271,22 @@
@Test(expected = JSONException::class)
fun jsonDeserialization_noId_threwException() {
- val expected = ClockRegistry.ClockSetting("ID", 500)
- val actual = ClockRegistry.ClockSetting.deserialize("{\"_applied_timestamp\":500}")
+ val expected = ClockSettings("ID", null, 500)
+ val actual = ClockSettings.deserialize("{\"_applied_timestamp\":500}")
assertEquals(expected, actual)
}
@Test
fun jsonSerialization_gotExpectedString() {
val expected = "{\"clockId\":\"ID\",\"_applied_timestamp\":500}"
- val actual = ClockRegistry.ClockSetting.serialize( ClockRegistry.ClockSetting("ID", 500))
+ val actual = ClockSettings.serialize(ClockSettings("ID", null, 500))
assertEquals(expected, actual)
}
@Test
fun jsonSerialization_noTimestamp_gotExpectedString() {
val expected = "{\"clockId\":\"ID\"}"
- val actual = ClockRegistry.ClockSetting.serialize( ClockRegistry.ClockSetting("ID", null))
+ val actual = ClockSettings.serialize(ClockSettings("ID", null, null))
assertEquals(expected, actual)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index a7588dd..cd2efc0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -114,7 +114,8 @@
@Test
fun defaultClock_events_onTimeTick() {
val clock = provider.createClock(DEFAULT_CLOCK_ID)
- clock.events.onTimeTick()
+ clock.smallClock.events.onTimeTick()
+ clock.largeClock.events.onTimeTick()
verify(mockSmallClockView).refreshTime()
verify(mockLargeClockView).refreshTime()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 2423f13..0a576de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -113,6 +113,9 @@
private lateinit var handler: Handler
@Mock
+ private lateinit var datePlugin: BcSmartspaceDataPlugin
+
+ @Mock
private lateinit var weatherPlugin: BcSmartspaceDataPlugin
@Mock
@@ -155,6 +158,7 @@
KeyguardBypassController.OnBypassStateChangedListener
private lateinit var deviceProvisionedListener: DeviceProvisionedListener
+ private lateinit var dateSmartspaceView: SmartspaceView
private lateinit var weatherSmartspaceView: SmartspaceView
private lateinit var smartspaceView: SmartspaceView
@@ -190,6 +194,8 @@
`when`(secureSettings.getUriFor(NOTIF_ON_LOCKSCREEN_SETTING))
.thenReturn(fakeNotifOnLockscreenSettingUri)
`when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(smartspaceSession)
+ `when`(datePlugin.getView(any())).thenReturn(
+ createDateSmartspaceView(), createDateSmartspaceView())
`when`(weatherPlugin.getView(any())).thenReturn(
createWeatherSmartspaceView(), createWeatherSmartspaceView())
`when`(plugin.getView(any())).thenReturn(createSmartspaceView(), createSmartspaceView())
@@ -221,6 +227,7 @@
executor,
bgExecutor,
handler,
+ Optional.of(datePlugin),
Optional.of(weatherPlugin),
Optional.of(plugin),
Optional.of(configPlugin),
@@ -275,7 +282,8 @@
// THEN the listener is registered to the underlying plugin
verify(plugin).registerListener(controllerListener)
- // The listener is registered only for the plugin, not the weather plugin.
+ // The listener is registered only for the plugin, not the date, or weather plugin.
+ verify(datePlugin, never()).registerListener(any())
verify(weatherPlugin, never()).registerListener(any())
}
@@ -289,7 +297,8 @@
// THEN the listener is subsequently registered
verify(plugin).registerListener(controllerListener)
- // The listener is registered only for the plugin, not the weather plugin.
+ // The listener is registered only for the plugin, not the date, or the weather plugin.
+ verify(datePlugin, never()).registerListener(any())
verify(weatherPlugin, never()).registerListener(any())
}
@@ -308,6 +317,7 @@
verify(plugin).registerSmartspaceEventNotifier(null)
verify(weatherPlugin).onTargetsAvailable(emptyList())
verify(weatherPlugin).registerSmartspaceEventNotifier(null)
+ verify(datePlugin).registerSmartspaceEventNotifier(null)
}
@Test
@@ -357,6 +367,7 @@
configChangeListener.onThemeChanged()
// We update the new text color to match the wallpaper color
+ verify(dateSmartspaceView).setPrimaryTextColor(anyInt())
verify(weatherSmartspaceView).setPrimaryTextColor(anyInt())
verify(smartspaceView).setPrimaryTextColor(anyInt())
}
@@ -384,6 +395,7 @@
statusBarStateListener.onDozeAmountChanged(0.1f, 0.7f)
// We pass that along to the view
+ verify(dateSmartspaceView).setDozeAmount(0.7f)
verify(weatherSmartspaceView).setDozeAmount(0.7f)
verify(smartspaceView).setDozeAmount(0.7f)
}
@@ -502,6 +514,8 @@
verify(plugin).onTargetsAvailable(eq(listOf(targets[0], targets[1], targets[2])))
// No filtering is applied for the weather plugin
verify(weatherPlugin).onTargetsAvailable(eq(targets))
+ // No targets needed for the date plugin
+ verify(datePlugin, never()).onTargetsAvailable(any())
}
@Test
@@ -633,6 +647,18 @@
private fun connectSession() {
if (controller.isDateWeatherDecoupled()) {
+ val dateView = controller.buildAndConnectDateView(fakeParent)
+ dateSmartspaceView = dateView as SmartspaceView
+ fakeParent.addView(dateView)
+ controller.stateChangeListener.onViewAttachedToWindow(dateView)
+
+ verify(dateSmartspaceView).setUiSurface(
+ BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
+ verify(dateSmartspaceView).registerDataProvider(datePlugin)
+
+ verify(dateSmartspaceView).setPrimaryTextColor(anyInt())
+ verify(dateSmartspaceView).setDozeAmount(0.5f)
+
val weatherView = controller.buildAndConnectWeatherView(fakeParent)
weatherSmartspaceView = weatherView as SmartspaceView
fakeParent.addView(weatherView)
@@ -686,6 +712,7 @@
verify(smartspaceView).setDozeAmount(0.5f)
if (controller.isDateWeatherDecoupled()) {
+ clearInvocations(dateSmartspaceView)
clearInvocations(weatherSmartspaceView)
}
clearInvocations(smartspaceView)
@@ -734,7 +761,38 @@
).thenReturn(if (value) 1 else 0)
}
- // Separate function for the weather view, which doesn't implement all functions in interface.
+ // Separate function for the date view, which implements a specific subset of all functions.
+ private fun createDateSmartspaceView(): SmartspaceView {
+ return spy(object : View(context), SmartspaceView {
+ override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {
+ }
+
+ override fun setPrimaryTextColor(color: Int) {
+ }
+
+ override fun setIsDreaming(isDreaming: Boolean) {
+ }
+
+ override fun setUiSurface(uiSurface: String) {
+ }
+
+ override fun setDozeAmount(amount: Float) {
+ }
+
+ override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {
+ }
+
+ override fun setFalsingManager(falsingManager: FalsingManager?) {
+ }
+
+ override fun setDnd(image: Drawable?, description: String?) {
+ }
+
+ override fun setNextAlarm(image: Drawable?, description: String?) {
+ }
+ })
+ }
+ // Separate function for the weather view, which implements a specific subset of all functions.
private fun createWeatherSmartspaceView(): SmartspaceView {
return spy(object : View(context), SmartspaceView {
override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
index af52459..701cf95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
@@ -117,6 +117,10 @@
extras.putBoolean(SHOW_WHEN_UNPROVISIONED_FLAG, true);
mNotification.extras = extras;
+ // GIVEN notification has the permission to display during setup
+ when(mIPackageManager.checkUidPermission(SETUP_NOTIF_PERMISSION, NOTIF_UID))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+
// THEN don't filter out the notification
assertFalse(mDeviceProvisionedFilter.shouldFilterOut(mEntry, 0));
}
@@ -126,10 +130,15 @@
// GIVEN device is unprovisioned
when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(false);
- // GIVEN notification does not have the flag to allow the notification during setup
+ // GIVEN notification has a flag to allow the notification during setup
Bundle extras = new Bundle();
+ extras.putBoolean(SHOW_WHEN_UNPROVISIONED_FLAG, true);
mNotification.extras = extras;
+ // GIVEN notification does NOT have permission to display during setup
+ when(mIPackageManager.checkUidPermission(SETUP_NOTIF_PERMISSION, NOTIF_UID))
+ .thenReturn(PackageManager.PERMISSION_DENIED);
+
// THEN filter out the notification
assertTrue(mDeviceProvisionedFilter.shouldFilterOut(mEntry, 0));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 36e76f4..85e8c34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -23,10 +23,12 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -45,6 +47,7 @@
import androidx.test.filters.SmallTest;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.dump.DumpManager;
@@ -67,6 +70,8 @@
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
import com.android.systemui.util.CarrierConfigTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
@@ -79,6 +84,9 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.List;
+
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
@@ -118,6 +126,12 @@
private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
@Mock
private DumpManager mDumpManager;
+ @Mock
+ private StatusBarWindowStateController mStatusBarWindowStateController;
+ @Mock
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
+ private List<StatusBarWindowStateListener> mStatusBarWindowStateListeners = new ArrayList<>();
public CollapsedStatusBarFragmentTest() {
super(CollapsedStatusBarFragment.class);
@@ -127,6 +141,14 @@
public void setup() {
injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
mDependency.injectMockDependency(DarkIconDispatcher.class);
+
+ // Keep the window state listeners so we can dispatch to them to test the status bar
+ // fragment's response.
+ doAnswer(invocation -> {
+ mStatusBarWindowStateListeners.add(invocation.getArgument(0));
+ return null;
+ }).when(mStatusBarWindowStateController).addListener(
+ any(StatusBarWindowStateListener.class));
}
@Test
@@ -414,6 +436,27 @@
assertFalse(contains);
}
+ @Test
+ public void testStatusBarIcons_hiddenThroughoutCameraLaunch() {
+ final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+
+ mockSecureCameraLaunch(fragment, true /* launched */);
+
+ // Status icons should be invisible or gone, but certainly not VISIBLE.
+ assertNotEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+ assertNotEquals(View.VISIBLE, getClockView().getVisibility());
+
+ mockSecureCameraLaunchFinished();
+
+ assertNotEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+ assertNotEquals(View.VISIBLE, getClockView().getVisibility());
+
+ mockSecureCameraLaunch(fragment, false /* launched */);
+
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+ assertEquals(View.VISIBLE, getClockView().getVisibility());
+ }
+
@Override
protected Fragment instantiate(Context context, String className, Bundle arguments) {
MockitoAnnotations.initMocks(this);
@@ -455,7 +498,9 @@
mOperatorNameViewControllerFactory,
mSecureSettings,
mExecutor,
- mDumpManager);
+ mDumpManager,
+ mStatusBarWindowStateController,
+ mKeyguardUpdateMonitor);
}
private void setUpDaggerComponent() {
@@ -478,6 +523,35 @@
mNotificationAreaInner);
}
+ /**
+ * Configure mocks to return values consistent with the secure camera animating itself launched
+ * over the keyguard.
+ */
+ private void mockSecureCameraLaunch(CollapsedStatusBarFragment fragment, boolean launched) {
+ when(mKeyguardUpdateMonitor.isSecureCameraLaunchedOverKeyguard()).thenReturn(launched);
+ when(mKeyguardStateController.isOccluded()).thenReturn(launched);
+
+ if (launched) {
+ fragment.onCameraLaunchGestureDetected(0 /* source */);
+ } else {
+ for (StatusBarWindowStateListener listener : mStatusBarWindowStateListeners) {
+ listener.onStatusBarWindowStateChanged(StatusBarManager.WINDOW_STATE_SHOWING);
+ }
+ }
+
+ fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+ }
+
+ /**
+ * Configure mocks to return values consistent with the secure camera showing over the keyguard
+ * with its launch animation finished.
+ */
+ private void mockSecureCameraLaunchFinished() {
+ for (StatusBarWindowStateListener listener : mStatusBarWindowStateListeners) {
+ listener.onStatusBarWindowStateChanged(StatusBarManager.WINDOW_STATE_HIDDEN);
+ }
+ }
+
private CollapsedStatusBarFragment resumeAndGetFragment() {
mFragments.dispatchResume();
processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 1b62d5c..bd24922 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -77,6 +78,7 @@
MobileIconsInteractorImpl(
connectionsRepository,
carrierConfigTracker,
+ logger = mock(),
userSetupRepository,
testScope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index a24e29ae..b91a4df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import androidx.test.filters.SmallTest
+import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
+import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE
import com.android.settingslib.graph.SignalDrawable
import com.android.settingslib.mobile.TelephonyIcons.THREE_G
import com.android.systemui.SysuiTestCase
@@ -122,6 +124,39 @@
}
@Test
+ fun contentDescription_notInService_usesNoPhone() =
+ testScope.runTest {
+ var latest: ContentDescription? = null
+ val job = underTest.contentDescription.onEach { latest = it }.launchIn(this)
+
+ interactor.isInService.value = false
+
+ assertThat((latest as ContentDescription.Resource).res)
+ .isEqualTo(PHONE_SIGNAL_STRENGTH_NONE)
+
+ job.cancel()
+ }
+
+ @Test
+ fun contentDescription_inService_usesLevel() =
+ testScope.runTest {
+ var latest: ContentDescription? = null
+ val job = underTest.contentDescription.onEach { latest = it }.launchIn(this)
+
+ interactor.isInService.value = true
+
+ interactor.level.value = 2
+ assertThat((latest as ContentDescription.Resource).res)
+ .isEqualTo(PHONE_SIGNAL_STRENGTH[2])
+
+ interactor.level.value = 0
+ assertThat((latest as ContentDescription.Resource).res)
+ .isEqualTo(PHONE_SIGNAL_STRENGTH[0])
+
+ job.cancel()
+ }
+
+ @Test
fun networkType_dataEnabled_groupIsRepresented() =
testScope.runTest {
val expected =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
index 3fe6983..e4c8fd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.shared.ui.view
+import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
@@ -118,6 +119,22 @@
assertThat(view.isIconVisible).isEqualTo(false)
}
+ @Test
+ fun getDrawingRect_takesTranslationIntoAccount() {
+ val view = createAndInitView()
+
+ view.translationX = 50f
+ view.translationY = 60f
+
+ val drawingRect = Rect()
+ view.getDrawingRect(drawingRect)
+
+ assertThat(drawingRect.left).isEqualTo(view.left + 50)
+ assertThat(drawingRect.right).isEqualTo(view.right + 50)
+ assertThat(drawingRect.top).isEqualTo(view.top + 60)
+ assertThat(drawingRect.bottom).isEqualTo(view.bottom + 60)
+ }
+
private fun createAndInitView(): ModernStatusBarView {
val view = ModernStatusBarView(context, null)
binding = TestBinding()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index dd04ac4..fc7436a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -79,6 +79,7 @@
@Mock private lateinit var viewUtil: ViewUtil
@Mock private lateinit var vibratorHelper: VibratorHelper
@Mock private lateinit var swipeGestureHandler: SwipeChipbarAwayGestureHandler
+ private lateinit var chipbarAnimator: TestChipbarAnimator
private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
private lateinit var fakeWakeLock: WakeLockFake
private lateinit var fakeClock: FakeSystemClock
@@ -98,6 +99,7 @@
fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
uiEventLoggerFake = UiEventLoggerFake()
+ chipbarAnimator = TestChipbarAnimator()
underTest =
ChipbarCoordinator(
@@ -109,6 +111,7 @@
configurationController,
dumpManager,
powerManager,
+ chipbarAnimator,
falsingManager,
falsingCollector,
swipeGestureHandler,
@@ -371,6 +374,26 @@
verify(vibratorHelper).vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK))
}
+ /** Regression test for b/266119467. */
+ @Test
+ fun displayView_animationFailure_viewsStillBecomeVisible() {
+ chipbarAnimator.allowAnimation = false
+
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Loading,
+ )
+ )
+
+ val view = getChipbarView()
+ assertThat(view.getInnerView().alpha).isEqualTo(1f)
+ assertThat(view.getStartIconView().alpha).isEqualTo(1f)
+ assertThat(view.getLoadingIcon().alpha).isEqualTo(1f)
+ assertThat(view.getChipTextView().alpha).isEqualTo(1f)
+ }
+
@Test
fun updateView_viewUpdated() {
// First, display a view
@@ -453,6 +476,25 @@
verify(windowManager).removeView(chipbarView)
}
+ /** Regression test for b/266209420. */
+ @Test
+ fun removeView_animationFailure_viewStillRemoved() {
+ chipbarAnimator.allowAnimation = false
+
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+ Text.Loaded("title text"),
+ endItem = ChipbarEndItem.Error,
+ ),
+ )
+ val chipbarView = getChipbarView()
+
+ underTest.removeView(DEVICE_ID, "test reason")
+
+ verify(windowManager).removeView(chipbarView)
+ }
+
@Test
fun swipeToDismiss_false_neverListensForGesture() {
underTest.displayView(
@@ -560,8 +602,9 @@
private fun ViewGroup.getStartIconView() = this.requireViewById<ImageView>(R.id.start_icon)
- private fun ViewGroup.getChipText(): String =
- (this.requireViewById<TextView>(R.id.text)).text as String
+ private fun ViewGroup.getChipTextView() = this.requireViewById<TextView>(R.id.text)
+
+ private fun ViewGroup.getChipText(): String = this.getChipTextView().text as String
private fun ViewGroup.getLoadingIcon(): View = this.requireViewById(R.id.loading)
@@ -574,6 +617,25 @@
verify(windowManager).addView(viewCaptor.capture(), any())
return viewCaptor.value as ViewGroup
}
+
+ /** Test class that lets us disallow animations. */
+ inner class TestChipbarAnimator : ChipbarAnimator() {
+ var allowAnimation: Boolean = true
+
+ override fun animateViewIn(innerView: ViewGroup, onAnimationEnd: Runnable): Boolean {
+ if (!allowAnimation) {
+ return false
+ }
+ return super.animateViewIn(innerView, onAnimationEnd)
+ }
+
+ override fun animateViewOut(innerView: ViewGroup, onAnimationEnd: Runnable): Boolean {
+ if (!allowAnimation) {
+ return false
+ }
+ return super.animateViewOut(innerView, onAnimationEnd)
+ }
+ }
}
private const val TIMEOUT = 10000
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index 034c618..ccf378a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -17,12 +17,17 @@
package com.android.systemui.user.data.repository
+import android.app.IActivityManager
+import android.app.UserSwitchObserver
import android.content.pm.UserInfo
+import android.os.IRemoteCallback
import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.util.settings.FakeSettings
@@ -39,7 +44,14 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -48,6 +60,8 @@
class UserRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var manager: UserManager
+ @Mock private lateinit var activityManager: IActivityManager
+ @Captor private lateinit var userSwitchObserver: ArgumentCaptor<UserSwitchObserver>
private lateinit var underTest: UserRepositoryImpl
@@ -214,6 +228,34 @@
assertThat(selectedUserInfo?.id).isEqualTo(1)
}
+ @Test
+ fun userSwitchingInProgress_registersOnlyOneUserSwitchObserver() = runSelfCancelingTest {
+ underTest = create(this)
+
+ underTest.userSwitchingInProgress.launchIn(this)
+ underTest.userSwitchingInProgress.launchIn(this)
+ underTest.userSwitchingInProgress.launchIn(this)
+
+ verify(activityManager, times(1)).registerUserSwitchObserver(any(), anyString())
+ }
+
+ @Test
+ fun userSwitchingInProgress_propagatesStateFromActivityManager() = runSelfCancelingTest {
+ underTest = create(this)
+ verify(activityManager)
+ .registerUserSwitchObserver(userSwitchObserver.capture(), anyString())
+
+ userSwitchObserver.value.onUserSwitching(0, mock(IRemoteCallback::class.java))
+
+ var mostRecentSwitchingValue = false
+ underTest.userSwitchingInProgress.onEach { mostRecentSwitchingValue = it }.launchIn(this)
+
+ assertThat(mostRecentSwitchingValue).isTrue()
+
+ userSwitchObserver.value.onUserSwitchComplete(0)
+ assertThat(mostRecentSwitchingValue).isFalse()
+ }
+
private fun createUserInfo(
id: Int,
isGuest: Boolean,
@@ -280,6 +322,8 @@
}
private fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
+ val featureFlags = FakeFeatureFlags()
+ featureFlags.set(FACE_AUTH_REFACTOR, true)
return UserRepositoryImpl(
appContext = context,
manager = manager,
@@ -288,6 +332,8 @@
backgroundDispatcher = IMMEDIATE,
globalSettings = globalSettings,
tracker = tracker,
+ activityManager = activityManager,
+ featureFlags = featureFlags,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 9d518ac..c6b4c92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -74,6 +74,7 @@
import android.testing.TestableLooper;
import android.util.Pair;
import android.util.SparseArray;
+import android.view.IWindowManager;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
@@ -392,7 +393,8 @@
syncExecutor,
mock(Handler.class),
mTaskViewTransitions,
- mock(SyncTransactionQueue.class));
+ mock(SyncTransactionQueue.class),
+ mock(IWindowManager.class));
mBubbleController.setExpandListener(mBubbleExpandListener);
spyOn(mBubbleController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
index 6357a09..3179285 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -20,6 +20,7 @@
import android.content.pm.LauncherApps;
import android.os.Handler;
import android.os.UserManager;
+import android.view.IWindowManager;
import android.view.WindowManager;
import com.android.internal.statusbar.IStatusBarService;
@@ -72,13 +73,14 @@
ShellExecutor shellMainExecutor,
Handler shellMainHandler,
TaskViewTransitions taskViewTransitions,
- SyncTransactionQueue syncQueue) {
+ SyncTransactionQueue syncQueue,
+ IWindowManager wmService) {
super(context, shellInit, shellCommandHandler, shellController, data, Runnable::run,
floatingContentCoordinator, dataRepository, statusBarService, windowManager,
windowManagerShellWrapper, userManager, launcherApps, bubbleLogger,
taskStackListener, shellTaskOrganizer, positioner, displayController,
oneHandedOptional, dragAndDropController, shellMainExecutor, shellMainHandler,
- new SyncExecutor(), taskViewTransitions, syncQueue);
+ new SyncExecutor(), taskViewTransitions, syncQueue, wmService);
setInflateSynchronously(true);
onInit();
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
similarity index 95%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
index f3e52de..044679d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
@@ -21,7 +21,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-class FakeBiometricRepository : BiometricRepository {
+class FakeBiometricSettingsRepository : BiometricSettingsRepository {
private val _isFingerprintEnrolled = MutableStateFlow<Boolean>(false)
override val isFingerprintEnrolled: StateFlow<Boolean> = _isFingerprintEnrolled.asStateFlow()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
new file mode 100644
index 0000000..d0383e9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
+import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
+import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Fake implementation of [KeyguardRepository] */
+class FakeKeyguardBouncerRepository : KeyguardBouncerRepository {
+ private val _primaryBouncerVisible = MutableStateFlow(false)
+ override val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
+ private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
+ override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
+ private val _primaryBouncerShowingSoon = MutableStateFlow(false)
+ override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
+ private val _primaryBouncerHide = MutableStateFlow(false)
+ override val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
+ private val _primaryBouncerStartingToHide = MutableStateFlow(false)
+ override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
+ private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
+ override val primaryBouncerStartingDisappearAnimation =
+ _primaryBouncerDisappearAnimation.asStateFlow()
+ private val _primaryBouncerScrimmed = MutableStateFlow(false)
+ override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
+ private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
+ override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
+ private val _keyguardPosition = MutableStateFlow(0f)
+ override val keyguardPosition = _keyguardPosition.asStateFlow()
+ private val _onScreenTurnedOff = MutableStateFlow(false)
+ override val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
+ private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
+ override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
+ private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
+ override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
+ private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
+ override val showMessage = _showMessage.asStateFlow()
+ private val _resourceUpdateRequests = MutableStateFlow(false)
+ override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
+ override val bouncerPromptReason = 0
+ override val bouncerErrorMessage: CharSequence? = null
+ private val _isAlternateBouncerVisible = MutableStateFlow(false)
+ override val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
+ override var lastAlternateBouncerVisibleTime: Long = 0L
+ private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false)
+ override val isAlternateBouncerUIAvailable = _isAlternateBouncerUIAvailable.asStateFlow()
+
+ override fun setPrimaryScrimmed(isScrimmed: Boolean) {
+ _primaryBouncerScrimmed.value = isScrimmed
+ }
+
+ override fun setPrimaryVisible(isVisible: Boolean) {
+ _primaryBouncerVisible.value = isVisible
+ }
+
+ override fun setAlternateVisible(isVisible: Boolean) {
+ _isAlternateBouncerVisible.value = isVisible
+ }
+
+ override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
+ _isAlternateBouncerUIAvailable.value = isAvailable
+ }
+
+ override fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
+ _primaryBouncerShow.value = keyguardBouncerModel
+ }
+
+ override fun setPrimaryShowingSoon(showingSoon: Boolean) {
+ _primaryBouncerShowingSoon.value = showingSoon
+ }
+
+ override fun setPrimaryHide(hide: Boolean) {
+ _primaryBouncerHide.value = hide
+ }
+
+ override fun setPrimaryStartingToHide(startingToHide: Boolean) {
+ _primaryBouncerStartingToHide.value = startingToHide
+ }
+
+ override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
+ _primaryBouncerDisappearAnimation.value = runnable
+ }
+
+ override fun setPanelExpansion(panelExpansion: Float) {
+ _panelExpansionAmount.value = panelExpansion
+ }
+
+ override fun setKeyguardPosition(keyguardPosition: Float) {
+ _keyguardPosition.value = keyguardPosition
+ }
+
+ override fun setResourceUpdateRequests(willUpdateResources: Boolean) {
+ _resourceUpdateRequests.value = willUpdateResources
+ }
+
+ override fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
+ _showMessage.value = bouncerShowMessageModel
+ }
+
+ override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
+ _keyguardAuthenticated.value = keyguardAuthenticated
+ }
+
+ override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
+ _isBackButtonEnabled.value = isBackButtonEnabled
+ }
+
+ override fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
+ _onScreenTurnedOff.value = onScreenTurnedOff
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt
index 6ae7c34..1403cea 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt
@@ -21,7 +21,7 @@
import android.view.Display
import java.util.concurrent.Executor
-class FakeDisplayTracker internal constructor(val context: Context) : DisplayTracker {
+class FakeDisplayTracker constructor(val context: Context) : DisplayTracker {
val displayManager: DisplayManager = context.getSystemService(DisplayManager::class.java)!!
override var defaultDisplayId: Int = Display.DEFAULT_DISPLAY
override var allDisplays: Array<Display> = displayManager.displays
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index ea5a302..1a8e244 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -39,6 +39,10 @@
private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null)
override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull()
+ private val _userSwitchingInProgress = MutableStateFlow(false)
+ override val userSwitchingInProgress: Flow<Boolean>
+ get() = _userSwitchingInProgress
+
override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
private var _isGuestUserAutoCreated: Boolean = false
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index de7184c..37069dc 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -371,6 +371,7 @@
final MagnificationConfig config = new MagnificationConfig.Builder()
.setMode(MAGNIFICATION_MODE_FULLSCREEN)
+ .setActivated(mMagnificationActivated)
.setScale(scale)
.setCenterX(getCenterX())
.setCenterY(getCenterY()).build();
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 129bc16..558c71b 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -284,6 +284,7 @@
}
synchronized (mLock) {
final int targetMode = config.getMode();
+ final boolean targetActivated = config.isActivated();
final PointF currentCenter = getCurrentMagnificationCenterLocked(displayId, targetMode);
final PointF magnificationCenter = new PointF(config.getCenterX(), config.getCenterY());
if (currentCenter != null) {
@@ -310,20 +311,30 @@
: config.getScale();
try {
setTransitionState(displayId, targetMode);
-
+ // Activate or deactivate target mode depending on config activated value
if (targetMode == MAGNIFICATION_MODE_WINDOW) {
screenMagnificationController.reset(displayId, false);
- windowMagnificationMgr.enableWindowMagnification(displayId,
- targetScale, magnificationCenter.x, magnificationCenter.y,
- animate ? STUB_ANIMATION_CALLBACK : null, id);
+ if (targetActivated) {
+ windowMagnificationMgr.enableWindowMagnification(displayId,
+ targetScale, magnificationCenter.x, magnificationCenter.y,
+ animate ? STUB_ANIMATION_CALLBACK : null, id);
+ } else {
+ windowMagnificationMgr.disableWindowMagnification(displayId, false);
+ }
} else if (targetMode == MAGNIFICATION_MODE_FULLSCREEN) {
windowMagnificationMgr.disableWindowMagnification(displayId, false, null);
- if (!screenMagnificationController.isRegistered(displayId)) {
- screenMagnificationController.register(displayId);
+ if (targetActivated) {
+ if (!screenMagnificationController.isRegistered(displayId)) {
+ screenMagnificationController.register(displayId);
+ }
+ screenMagnificationController.setScaleAndCenter(displayId, targetScale,
+ magnificationCenter.x, magnificationCenter.y, animate,
+ id);
+ } else {
+ if (screenMagnificationController.isRegistered(displayId)) {
+ screenMagnificationController.reset(displayId, false);
+ }
}
- screenMagnificationController.setScaleAndCenter(displayId, targetScale,
- magnificationCenter.x, magnificationCenter.y, animate,
- id);
}
} finally {
// Reset transition state after enabling target mode.
@@ -454,6 +465,7 @@
if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_WINDOW)) {
final MagnificationConfig config = new MagnificationConfig.Builder()
.setMode(MAGNIFICATION_MODE_WINDOW)
+ .setActivated(getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId))
.setScale(getWindowMagnificationMgr().getScale(displayId))
.setCenterX(bounds.exactCenterX())
.setCenterY(bounds.exactCenterY()).build();
@@ -834,6 +846,7 @@
new MagnificationConfig.Builder();
Region region = new Region();
configBuilder.setMode(MAGNIFICATION_MODE_FULLSCREEN)
+ .setActivated(screenMagnificationController.isActivated(mDisplayId))
.setScale(screenMagnificationController.getScale(mDisplayId))
.setCenterX(screenMagnificationController.getCenterX(mDisplayId))
.setCenterY(screenMagnificationController.getCenterY(mDisplayId));
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
index 75fe026..5cf2a63 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
@@ -79,6 +79,7 @@
final FullScreenMagnificationController fullScreenMagnificationController =
mController.getFullScreenMagnificationController();
builder.setMode(mode)
+ .setActivated(mController.isActivated(displayId, MAGNIFICATION_MODE_FULLSCREEN))
.setScale(fullScreenMagnificationController.getScale(displayId))
.setCenterX(fullScreenMagnificationController.getCenterX(displayId))
.setCenterY(fullScreenMagnificationController.getCenterY(displayId));
@@ -86,9 +87,13 @@
final WindowMagnificationManager windowMagnificationManager =
mController.getWindowMagnificationMgr();
builder.setMode(mode)
+ .setActivated(mController.isActivated(displayId, MAGNIFICATION_MODE_WINDOW))
.setScale(windowMagnificationManager.getScale(displayId))
.setCenterX(windowMagnificationManager.getCenterX(displayId))
.setCenterY(windowMagnificationManager.getCenterY(displayId));
+ } else {
+ // For undefined mode, set enabled to false
+ builder.setActivated(false);
}
return builder.build();
}
@@ -118,15 +123,26 @@
if (configMode == MAGNIFICATION_MODE_DEFAULT) {
configMode = getControllingMode(displayId);
}
+ // Check should activate or deactivate the target mode in config
+ boolean configActivated = config.isActivated();
if (configMode == MAGNIFICATION_MODE_FULLSCREEN) {
- return setScaleAndCenterForFullScreenMagnification(displayId, config.getScale(),
- config.getCenterX(), config.getCenterY(),
- animate, id);
+ if (configActivated) {
+ return setScaleAndCenterForFullScreenMagnification(displayId, config.getScale(),
+ config.getCenterX(), config.getCenterY(),
+ animate, id);
+ } else {
+ return resetFullscreenMagnification(displayId, animate);
+ }
} else if (configMode == MAGNIFICATION_MODE_WINDOW) {
- return mController.getWindowMagnificationMgr().enableWindowMagnification(displayId,
- config.getScale(), config.getCenterX(), config.getCenterY(),
- animate ? STUB_ANIMATION_CALLBACK : null,
- id);
+ if (configActivated) {
+ return mController.getWindowMagnificationMgr().enableWindowMagnification(displayId,
+ config.getScale(), config.getCenterX(), config.getCenterY(),
+ animate ? STUB_ANIMATION_CALLBACK : null,
+ id);
+ } else {
+ return mController.getWindowMagnificationMgr()
+ .disableWindowMagnification(displayId, false);
+ }
}
return false;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index 7591329..2d5f894 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -632,7 +632,7 @@
* @param clear {@true} Clears the state of window magnification.
* @return {@code true} if the magnification is turned to be disabled successfully
*/
- boolean disableWindowMagnification(int displayId, boolean clear) {
+ public boolean disableWindowMagnification(int displayId, boolean clear) {
return disableWindowMagnification(displayId, clear, STUB_ANIMATION_CALLBACK);
}
@@ -697,7 +697,6 @@
* @param displayId The logical display id.
* @return {@code true} if the window magnification is enabled.
*/
- @VisibleForTesting
public boolean isWindowMagnifierEnabled(int displayId) {
synchronized (mLock) {
WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
diff --git a/services/api/current.txt b/services/api/current.txt
index f7d6ee9..70ee3b8 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -225,6 +225,14 @@
}
+package com.android.server.security {
+
+ public final class FileIntegrityLocal {
+ method public static void setUpFsVerity(@NonNull String) throws java.io.IOException;
+ }
+
+}
+
package com.android.server.stats {
public final class StatsHelper {
diff --git a/services/art-profile b/services/art-profile
index 2bb85a4..132b9ab 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -20484,12 +20484,12 @@
Lcom/android/server/usage/UsageStatsShellCommand;
Lcom/android/server/usage/UserUsageStatsService$StatsUpdatedListener;
Lcom/android/server/usb/UsbAlsaJackDetector;
+Lcom/android/server/usb/UsbAlsaMidiDevice$2;
+Lcom/android/server/usb/UsbAlsaMidiDevice$3;
+Lcom/android/server/usb/UsbAlsaMidiDevice$InputReceiverProxy;
+Lcom/android/server/usb/UsbAlsaMidiDevice;
Lcom/android/server/usb/UsbDeviceManager;
Lcom/android/server/usb/UsbHostManager;
-Lcom/android/server/usb/UsbMidiDevice$2;
-Lcom/android/server/usb/UsbMidiDevice$3;
-Lcom/android/server/usb/UsbMidiDevice$InputReceiverProxy;
-Lcom/android/server/usb/UsbMidiDevice;
Lcom/android/server/usb/descriptors/UsbDescriptor;
Lcom/android/server/usb/descriptors/UsbInterfaceDescriptor;
Lcom/android/server/utils/AlarmQueue$1;
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index b41664f..7a93719 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -52,6 +52,7 @@
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.Trace;
import android.os.UEventObserver;
import android.os.UserHandle;
@@ -574,6 +575,10 @@
EventLog.writeEvent(EventLogTags.BATTERY_STATUS,
mHealthInfo.batteryStatus, mHealthInfo.batteryHealth, mHealthInfo.batteryPresent ? 1 : 0,
mPlugType, mHealthInfo.batteryTechnology);
+ SystemProperties.set(
+ "debug.tracing.battery_status",
+ Integer.toString(mHealthInfo.batteryStatus));
+ SystemProperties.set("debug.tracing.plug_type", Integer.toString(mPlugType));
}
if (mHealthInfo.batteryLevel != mLastBatteryLevel) {
// Don't do this just from voltage or temperature changes, that is
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index f93f504..b6a2a0e 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -96,7 +96,6 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
/**
@@ -287,7 +286,7 @@
* - dynamically installed mobile bundled apps (MBAs) (new in Android U)
*/
public void recordMeasurementsForAllPackages() {
- // check if we should record the resulting measurements
+ // check if we should measure and record
long currentTimeMs = System.currentTimeMillis();
if ((currentTimeMs - mMeasurementsLastRecordedMs) < RECORD_MEASUREMENTS_COOLDOWN_MS) {
Slog.d(TAG, "Skip measurement since the last measurement was only taken at "
@@ -1230,10 +1229,8 @@
* JobService to measure all covered binaries and record result to Westworld.
*/
public static class UpdateMeasurementsJobService extends JobService {
- private static AtomicBoolean sScheduled = new AtomicBoolean();
private static long sTimeLastRanMs = 0;
- private static final int DO_BINARY_MEASUREMENTS_JOB_ID =
- UpdateMeasurementsJobService.class.hashCode();
+ private static final int DO_BINARY_MEASUREMENTS_JOB_ID = 1740526926;
@Override
public boolean onStartJob(JobParameters params) {
@@ -1256,7 +1253,6 @@
return;
}
sTimeLastRanMs = System.currentTimeMillis();
- sScheduled.set(false);
jobFinished(params, false);
}).start();
@@ -1277,7 +1273,7 @@
return;
}
- if (sScheduled.get()) {
+ if (jobScheduler.getPendingJob(DO_BINARY_MEASUREMENTS_JOB_ID) != null) {
Slog.d(TAG, "A measurement job has already been scheduled.");
return;
}
@@ -1303,7 +1299,6 @@
Slog.e(TAG, "Failed to schedule job to measure binaries.");
return;
}
- sScheduled.set(true);
Slog.d(TAG, TextUtils.formatSimple(
"Job %d to measure binaries was scheduled successfully.",
DO_BINARY_MEASUREMENTS_JOB_ID));
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 4854c37..4b76127 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -333,6 +333,11 @@
// Update ownership for system applications and the installers eligible to update them.
private final ArrayMap<String, String> mUpdateOwnersForSystemApps = new ArrayMap<>();
+ // Set of package names that should not be marked as "stopped" during initial device boot
+ // or when adding a new user. A new package not contained in this set will be
+ // marked as stopped by the system
+ @NonNull private final Set<String> mInitialNonStoppedSystemPackages = new ArraySet<>();
+
/**
* Map of system pre-defined, uniquely named actors; keys are namespace,
* value maps actor name to package name.
@@ -527,6 +532,10 @@
? null : mOverlayConfigSignaturePackage;
}
+ public Set<String> getInitialNonStoppedSystemPackages() {
+ return mInitialNonStoppedSystemPackages;
+ }
+
/**
* Only use for testing. Do NOT use in production code.
* @param readPermissions false to create an empty SystemConfig; true to read the permissions.
@@ -1445,6 +1454,19 @@
}
XmlUtils.skipCurrentTag(parser);
} break;
+ case "initial-package-state": {
+ String pkgName = parser.getAttributeValue(null, "package");
+ String stopped = parser.getAttributeValue(null, "stopped");
+ if (TextUtils.isEmpty(pkgName)) {
+ Slog.w(TAG, "<" + name + "> without package in " + permFile
+ + " at " + parser.getPositionDescription());
+ } else if (TextUtils.isEmpty(stopped)) {
+ Slog.w(TAG, "<" + name + "> without stopped in " + permFile
+ + " at " + parser.getPositionDescription());
+ } else if (!Boolean.parseBoolean(stopped)) {
+ mInitialNonStoppedSystemPackages.add(pkgName);
+ }
+ }
default: {
Slog.w(TAG, "Tag " + name + " is unknown in "
+ permFile + " at " + parser.getPositionDescription());
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index ae65dcb..cc8aec7 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -69,5 +69,11 @@
],
"file_patterns": ["ClipboardService\\.java"]
}
+ ],
+ "postsubmit": [
+ {
+ "name": "BinaryTransparencyHostTest",
+ "file_patterns": ["BinaryTransparencyService\\.java"]
+ }
]
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index f0b168d..ce6a6c8 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -175,6 +175,8 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.service.voice.HotwordDetectionService;
+import android.service.voice.VisualQueryDetectionService;
import android.stats.devicepolicy.DevicePolicyEnums;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -3808,6 +3810,17 @@
inSharedIsolatedProcess);
}
+ // TODO(b/265746493): Special case for HotwordDetectionService and
+ // VisualQueryDetectionService. Need a cleaner way to append this seInfo.
+ private String generateAdditionalSeInfoFromService(Intent service) {
+ if (service != null && service.getAction() != null
+ && (service.getAction().equals(HotwordDetectionService.SERVICE_INTERFACE)
+ || service.getAction().equals(VisualQueryDetectionService.SERVICE_INTERFACE))) {
+ return ":isolatedComputeApp";
+ }
+ return "";
+ }
+
private ServiceLookupResult retrieveServiceLocked(Intent service,
String instanceName, boolean isSdkSandboxService, int sdkSandboxClientAppUid,
String sdkSandboxClientAppPackage, String resolvedType,
@@ -3925,6 +3938,7 @@
r.mRecentCallingPackage = callingPackage;
r.mRecentCallingUid = callingUid;
}
+ r.appInfo.seInfo += generateAdditionalSeInfoFromService(service);
return new ServiceLookupResult(r, resolution.getAlias());
}
@@ -4150,6 +4164,7 @@
return null;
}
}
+ r.appInfo.seInfo += generateAdditionalSeInfoFromService(service);
return new ServiceLookupResult(r, resolution.getAlias());
}
return null;
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index ef79351..cdc5d81 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -770,10 +770,6 @@
// initialized in the constructor.
public int CUR_MAX_EMPTY_PROCESSES;
- /** @see mEnforceReceiverExportedFlagRequirement */
- private static final String KEY_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT =
- "enforce_exported_flag_requirement";
-
/** @see #mNoKillCachedProcessesUntilBootCompleted */
private static final String KEY_NO_KILL_CACHED_PROCESSES_UNTIL_BOOT_COMPLETED =
"no_kill_cached_processes_until_boot_completed";
@@ -782,9 +778,6 @@
private static final String KEY_NO_KILL_CACHED_PROCESSES_POST_BOOT_COMPLETED_DURATION_MILLIS =
"no_kill_cached_processes_post_boot_completed_duration_millis";
- /** @see mEnforceReceiverExportedFlagRequirement */
- private static final boolean DEFAULT_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT = true;
-
/** @see #mNoKillCachedProcessesUntilBootCompleted */
private static final boolean DEFAULT_NO_KILL_CACHED_PROCESSES_UNTIL_BOOT_COMPLETED = true;
@@ -793,15 +786,6 @@
DEFAULT_NO_KILL_CACHED_PROCESSES_POST_BOOT_COMPLETED_DURATION_MILLIS = 600_000;
/**
- * If true, enforce the requirement that dynamically registered receivers specify one of
- * {@link android.content.Context#RECEIVER_EXPORTED} or
- * {@link android.content.Context#RECEIVER_NOT_EXPORTED} if registering for any non-system
- * broadcasts.
- */
- volatile boolean mEnforceReceiverExportedFlagRequirement =
- DEFAULT_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT;
-
- /**
* If true, do not kill excessive cached processes proactively, until user-0 is unlocked.
* @see #mNoKillCachedProcessesPostBootCompletedDurationMillis
*/
@@ -1112,9 +1096,6 @@
case KEY_NO_KILL_CACHED_PROCESSES_UNTIL_BOOT_COMPLETED:
updateNoKillCachedProcessesUntilBootCompleted();
break;
- case KEY_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT:
- updateEnforceReceiverExportedFlagRequirement();
- break;
case KEY_NO_KILL_CACHED_PROCESSES_POST_BOOT_COMPLETED_DURATION_MILLIS:
updateNoKillCachedProcessesPostBootCompletedDurationMillis();
break;
@@ -1616,13 +1597,6 @@
DEFAULT_DEFER_BOOT_COMPLETED_BROADCAST);
}
- private void updateEnforceReceiverExportedFlagRequirement() {
- mEnforceReceiverExportedFlagRequirement = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- KEY_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT,
- DEFAULT_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT);
- }
-
private void updateNoKillCachedProcessesUntilBootCompleted() {
mNoKillCachedProcessesUntilBootCompleted = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -2002,8 +1976,6 @@
pw.print("="); pw.println(mPrioritizeAlarmBroadcasts);
pw.print(" "); pw.print(KEY_NO_KILL_CACHED_PROCESSES_UNTIL_BOOT_COMPLETED);
pw.print("="); pw.println(mNoKillCachedProcessesUntilBootCompleted);
- pw.print(" "); pw.print(KEY_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT);
- pw.print("="); pw.println(mEnforceReceiverExportedFlagRequirement);
pw.print(" "); pw.print(KEY_NO_KILL_CACHED_PROCESSES_POST_BOOT_COMPLETED_DURATION_MILLIS);
pw.print("="); pw.println(mNoKillCachedProcessesPostBootCompletedDurationMillis);
pw.print(" "); pw.print(KEY_MAX_EMPTY_TIME_MILLIS);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 57a89e3..b272502 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -13588,8 +13588,7 @@
// broadcasts, or the receiver is null (a sticky broadcast). Sticky broadcasts should
// not be used generally, so we will be marking them as exported by default
boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
- DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid)
- && mConstants.mEnforceReceiverExportedFlagRequirement;
+ DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid);
// STOPSHIP(b/259139792): Allow apps that are currently targeting U and in process of
// updating their receivers to be exempt from this requirement until their receivers
// are flagged.
@@ -18974,14 +18973,20 @@
return null;
}
- // Starts with all displays but DEFAULT_DISPLAY
- int[] displayIds = new int[allDisplays.length - 1];
+ boolean allowOnDefaultDisplay = UserManager
+ .isVisibleBackgroundUsersOnDefaultDisplayEnabled();
+ int displaysSize = allDisplays.length;
+ if (!allowOnDefaultDisplay) {
+ displaysSize--;
+ }
+ int[] displayIds = new int[displaysSize];
- // TODO(b/247592632): check for other properties like isSecure or proper display type
int numberValidDisplays = 0;
for (Display display : allDisplays) {
int displayId = display.getDisplayId();
- if (display.isValid() && displayId != Display.DEFAULT_DISPLAY) {
+ // TODO(b/247592632): check other properties like isSecure or proper display type
+ if (display.isValid()
+ && (allowOnDefaultDisplay || displayId != Display.DEFAULT_DISPLAY)) {
displayIds[numberValidDisplays++] = displayId;
}
}
@@ -18993,14 +18998,15 @@
// STOPSHIP: if not removed, it should at least be unit tested
String testingProp = "fw.display_ids_for_starting_users_for_testing_purposes";
int displayId = SystemProperties.getInt(testingProp, Display.DEFAULT_DISPLAY);
- if (displayId != Display.DEFAULT_DISPLAY && displayId > 0) {
- Slogf.w(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): no valid "
+ if (allowOnDefaultDisplay && displayId == Display.DEFAULT_DISPLAY
+ || displayId > 0) {
+ Slogf.w(TAG, "getDisplayIdsForStartingVisibleBackgroundUsers(): no valid "
+ "display found, but returning %d as set by property %s", displayId,
testingProp);
return new int[] { displayId };
}
- Slogf.e(TAG, "getDisplayIdsForStartingBackgroundUsers(): no valid display on %s",
- Arrays.toString(allDisplays));
+ Slogf.e(TAG, "getDisplayIdsForStartingVisibleBackgroundUsers(): no valid display"
+ + " on %s", Arrays.toString(allDisplays));
return null;
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index b1a01cc..7290f32 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -273,7 +273,8 @@
performReceiveLocked(oldRecord.resultToApp, oldRecord.resultTo,
oldRecord.intent,
Activity.RESULT_CANCELED, null, null,
- false, false, oldRecord.userId, oldRecord.callingUid, r.callingUid,
+ false, false, r.shareIdentity, oldRecord.userId,
+ oldRecord.callingUid, r.callingUid, r.callerPackage,
SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0);
} catch (RemoteException e) {
Slog.w(TAG, "Failure ["
@@ -402,7 +403,9 @@
prepareReceiverIntent(r.intent, r.curFilteredExtras),
r.curReceiver, null /* compatInfo (unused but need to keep method signature) */,
r.resultCode, r.resultData, r.resultExtras, r.ordered, assumeDelivered,
- r.userId, app.mState.getReportedProcState()));
+ r.userId, r.shareIdentity ? r.callingUid : Process.INVALID_UID,
+ r.shareIdentity ? r.callerPackage : null,
+ app.mState.getReportedProcState()));
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Process cur broadcast " + r + " DELIVERED for app " + app);
started = true;
@@ -726,9 +729,15 @@
public void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
Intent intent, int resultCode, String data, Bundle extras,
- boolean ordered, boolean sticky, int sendingUser,
- int receiverUid, int callingUid, long dispatchDelay,
- long receiveDelay) throws RemoteException {
+ boolean ordered, boolean sticky, boolean shareIdentity, int sendingUser,
+ int receiverUid, int callingUid, String callingPackage,
+ long dispatchDelay, long receiveDelay) throws RemoteException {
+ // If the broadcaster opted-in to sharing their identity, then expose package visibility for
+ // the receiver.
+ if (shareIdentity) {
+ mService.mPackageManagerInt.grantImplicitAccess(sendingUser, intent,
+ UserHandle.getAppId(receiverUid), callingUid, true);
+ }
// Send the intent to the receiver asynchronously using one-way binder calls.
if (app != null) {
final IApplicationThread thread = app.getThread();
@@ -740,6 +749,8 @@
thread.scheduleReceiverList(mReceiverBatch.registeredReceiver(
receiver, intent, resultCode,
data, extras, ordered, sticky, assumeDelivered, sendingUser,
+ shareIdentity ? callingUid : Process.INVALID_UID,
+ shareIdentity ? callingPackage : null,
app.mState.getReportedProcState()));
} catch (RemoteException ex) {
// Failed to call into the process. It's either dying or wedged. Kill it gently.
@@ -845,8 +856,8 @@
maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
prepareReceiverIntent(r.intent, filteredExtras), r.resultCode, r.resultData,
- r.resultExtras, r.ordered, r.initialSticky, r.userId,
- filter.receiverList.uid, r.callingUid,
+ r.resultExtras, r.ordered, r.initialSticky, r.shareIdentity, r.userId,
+ filter.receiverList.uid, r.callingUid, r.callerPackage,
r.dispatchTime - r.enqueueTime,
r.receiverTime - r.dispatchTime);
// parallel broadcasts are fire-and-forget, not bookended by a call to
@@ -1130,8 +1141,8 @@
r.mIsReceiverAppRunning = true;
performReceiveLocked(r.resultToApp, r.resultTo,
new Intent(r.intent), r.resultCode,
- r.resultData, r.resultExtras, false, false, r.userId,
- r.callingUid, r.callingUid,
+ r.resultData, r.resultExtras, false, false, r.shareIdentity,
+ r.userId, r.callingUid, r.callingUid, r.callerPackage,
r.dispatchTime - r.enqueueTime,
now - r.dispatchTime);
logBootCompletedBroadcastCompletionLatencyIfPossible(r);
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 99e2ac7..b952ce0 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -893,6 +893,8 @@
batch.schedule(((BroadcastFilter) receiver).receiverList.receiver,
receiverIntent, r.resultCode, r.resultData, r.resultExtras,
r.ordered, r.initialSticky, assumeDelivered, r.userId,
+ r.shareIdentity ? r.callingUid : Process.INVALID_UID,
+ r.shareIdentity ? r.callerPackage : null,
app.mState.getReportedProcState(), r, index);
// TODO: consider making registered receivers of unordered
// broadcasts report results to detect ANRs
@@ -903,7 +905,9 @@
} else {
batch.schedule(receiverIntent, ((ResolveInfo) receiver).activityInfo,
null, r.resultCode, r.resultData, r.resultExtras, r.ordered, assumeDelivered,
- r.userId, app.mState.getReportedProcState(), r, index);
+ r.userId, r.shareIdentity ? r.callingUid : Process.INVALID_UID,
+ r.shareIdentity ? r.callerPackage : null,
+ app.mState.getReportedProcState(), r, index);
if (assumeDelivered) {
batch.success(r, index, BroadcastRecord.DELIVERY_DELIVERED, "assuming delivered");
return true;
@@ -946,6 +950,11 @@
final BroadcastRecord r = cookie.r;
final int index = cookie.index;
final Object receiver = r.receivers.get(index);
+
+ if (r.shareIdentity) {
+ mService.mPackageManagerInt.grantImplicitAccess(r.userId, r.intent,
+ UserHandle.getAppId(app.uid), r.callingUid, true);
+ }
if (receiver instanceof BroadcastFilter) {
notifyScheduleRegisteredReceiver(queue.app, r, (BroadcastFilter) receiver);
} else {
@@ -1055,12 +1064,19 @@
if (thread != null) {
mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
app, OOM_ADJ_REASON_FINISH_RECEIVER);
+ if (r.shareIdentity) {
+ mService.mPackageManagerInt.grantImplicitAccess(r.userId, r.intent,
+ UserHandle.getAppId(app.uid), r.callingUid, true);
+ }
try {
final boolean assumeDelivered = true;
thread.scheduleReceiverList(mReceiverBatch.registeredReceiver(
r.resultTo, r.intent,
r.resultCode, r.resultData, r.resultExtras, false, r.initialSticky,
- assumeDelivered, r.userId, app.mState.getReportedProcState()));
+ assumeDelivered, r.userId,
+ r.shareIdentity ? r.callingUid : Process.INVALID_UID,
+ r.shareIdentity ? r.callerPackage : null,
+ app.mState.getReportedProcState()));
} catch (RemoteException e) {
final String msg = "Failed to schedule result of " + r + " via " + app + ": " + e;
logw(msg);
diff --git a/services/core/java/com/android/server/am/BroadcastReceiverBatch.java b/services/core/java/com/android/server/am/BroadcastReceiverBatch.java
index 226647c..153403a 100644
--- a/services/core/java/com/android/server/am/BroadcastReceiverBatch.java
+++ b/services/core/java/com/android/server/am/BroadcastReceiverBatch.java
@@ -171,8 +171,8 @@
// Add a ReceiverInfo for a registered receiver.
void schedule(@Nullable IIntentReceiver receiver, Intent intent,
int resultCode, @Nullable String data, @Nullable Bundle extras, boolean ordered,
- boolean sticky, boolean assumeDelivered, int sendingUser, int processState,
- @Nullable BroadcastRecord r, int index) {
+ boolean sticky, boolean assumeDelivered, int sendingUser, int callingUid,
+ String callingPackage, int processState, @Nullable BroadcastRecord r, int index) {
ReceiverInfo ri = new ReceiverInfo();
ri.intent = intent;
ri.data = data;
@@ -185,6 +185,9 @@
ri.receiver = receiver;
ri.ordered = ordered;
ri.sticky = sticky;
+ ri.sentFromUid = callingUid;
+ ri.sentFromPackage = callingPackage;
+
mReceivers.add(ri);
mCookies.add(cookiePool.next().set(r, index));
}
@@ -192,7 +195,8 @@
void schedule(@Nullable Intent intent, @Nullable ActivityInfo activityInfo,
@Nullable CompatibilityInfo compatInfo, int resultCode, @Nullable String data,
@Nullable Bundle extras, boolean sync, boolean assumeDelivered, int sendingUser,
- int processState, @Nullable BroadcastRecord r, int index) {
+ int callingUid, @Nullable String callingPackage, int processState,
+ @Nullable BroadcastRecord r, int index) {
ReceiverInfo ri = new ReceiverInfo();
ri.intent = intent;
ri.data = data;
@@ -205,6 +209,8 @@
ri.activityInfo = activityInfo;
ri.compatInfo = compatInfo;
ri.sync = sync;
+ ri.sentFromUid = callingUid;
+ ri.sentFromPackage = callingPackage;
mReceivers.add(ri);
mCookies.add(cookiePool.next().set(r, index));
}
@@ -217,20 +223,21 @@
ArrayList<ReceiverInfo> registeredReceiver(@Nullable IIntentReceiver receiver,
@Nullable Intent intent, int resultCode, @Nullable String data,
@Nullable Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
- int sendingUser, int processState) {
+ int sendingUser, int callingUid, String callingPackage, int processState) {
reset();
schedule(receiver, intent, resultCode, data, extras, ordered, sticky, assumeDelivered,
- sendingUser, processState, null, 0);
+ sendingUser, callingUid, callingPackage, processState, null, 0);
return receivers();
}
ArrayList<ReceiverInfo> manifestReceiver(@Nullable Intent intent,
@Nullable ActivityInfo activityInfo, @Nullable CompatibilityInfo compatInfo,
int resultCode, @Nullable String data, @Nullable Bundle extras, boolean sync,
- boolean assumeDelivered, int sendingUser, int processState) {
+ boolean assumeDelivered, int sendingUser, int callingUid, String callingPackage,
+ int processState) {
reset();
schedule(intent, activityInfo, compatInfo, resultCode, data, extras, sync, assumeDelivered,
- sendingUser, processState, null, 0);
+ sendingUser, callingUid, callingPackage, processState, null, 0);
return receivers();
}
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 4304377..6035ad9 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -89,6 +89,7 @@
final boolean initialSticky; // initial broadcast from register to sticky?
final boolean prioritized; // contains more than one priority tranche
final boolean deferUntilActive; // infinitely deferrable broadcast
+ final boolean shareIdentity; // whether the broadcaster's identity should be shared
final int userId; // user id this broadcast was for
final @Nullable String resolvedType; // the resolved data type
final @Nullable String[] requiredPermissions; // permissions the caller has required
@@ -417,6 +418,7 @@
pushMessage = options != null && options.isPushMessagingBroadcast();
pushMessageOverQuota = options != null && options.isPushMessagingOverQuotaBroadcast();
interactive = options != null && options.isInteractive();
+ shareIdentity = options != null && options.isShareIdentityEnabled();
this.filterExtrasForReceiver = filterExtrasForReceiver;
}
@@ -481,6 +483,7 @@
pushMessage = from.pushMessage;
pushMessageOverQuota = from.pushMessageOverQuota;
interactive = from.interactive;
+ shareIdentity = from.shareIdentity;
filterExtrasForReceiver = from.filterExtrasForReceiver;
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index e02dda6..3643db0 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1898,7 +1898,7 @@
// For short FGS.
adjType = "fg-service-short";
// We use MEDIUM_APP_ADJ + 1 so we can tell apart EJ
- // (which uses MEDIUM_APP_ADJ + 1)
+ // (which uses MEDIUM_APP_ADJ + 2)
// from short-FGS.
// (We use +1 and +2, not +0 and +1, to be consistent with the following
// RECENT_FOREGROUND_APP_ADJ tweak)
@@ -2319,6 +2319,14 @@
&& adj >= (lbAdj = PERCEPTIBLE_LOW_APP_ADJ)) {
newAdj = PERCEPTIBLE_LOW_APP_ADJ;
} else if ((cr.flags & Context.BIND_ALMOST_PERCEPTIBLE) != 0
+ && (cr.flags & Context.BIND_NOT_FOREGROUND) == 0
+ && clientAdj < PERCEPTIBLE_APP_ADJ
+ && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) {
+ // This is for user-initiated jobs.
+ // We use APP_ADJ + 1 here, so we can tell them apart from FGS.
+ newAdj = PERCEPTIBLE_APP_ADJ + 1;
+ } else if ((cr.flags & Context.BIND_ALMOST_PERCEPTIBLE) != 0
+ && (cr.flags & Context.BIND_NOT_FOREGROUND) != 0
&& clientAdj < PERCEPTIBLE_APP_ADJ
&& adj >= (lbAdj = (PERCEPTIBLE_MEDIUM_APP_ADJ + 2))) {
// This is for expedited jobs.
diff --git a/services/core/java/com/android/server/am/SameProcessApplicationThread.java b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
index 082e8e0..dcb02ea 100644
--- a/services/core/java/com/android/server/am/SameProcessApplicationThread.java
+++ b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
@@ -48,11 +48,12 @@
@Override
public void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo,
int resultCode, String data, Bundle extras, boolean ordered, boolean assumeDelivered,
- int sendingUser, int processState) {
+ int sendingUser, int processState, int sentFromUid, String sentFromPackage) {
mHandler.post(() -> {
try {
mWrapped.scheduleReceiver(intent, info, compatInfo, resultCode, data, extras,
- ordered, assumeDelivered, sendingUser, processState);
+ ordered, assumeDelivered, sendingUser, processState, sentFromUid,
+ sentFromPackage);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -62,11 +63,12 @@
@Override
public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, int resultCode,
String data, Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
- int sendingUser, int processState) {
+ int sendingUser, int processState, int sentFromUid, String sentFromPackage) {
mHandler.post(() -> {
try {
mWrapped.scheduleRegisteredReceiver(receiver, intent, resultCode, data, extras,
- ordered, sticky, assumeDelivered, sendingUser, processState);
+ ordered, sticky, assumeDelivered, sendingUser, processState, sentFromUid,
+ sentFromPackage);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -80,11 +82,11 @@
if (r.registered) {
scheduleRegisteredReceiver(r.receiver, r.intent,
r.resultCode, r.data, r.extras, r.ordered, r.sticky, r.assumeDelivered,
- r.sendingUser, r.processState);
+ r.sendingUser, r.processState, r.sentFromUid, r.sentFromPackage);
} else {
scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
r.resultCode, r.data, r.extras, r.sync, r.assumeDelivered,
- r.sendingUser, r.processState);
+ r.sendingUser, r.processState, r.sentFromUid, r.sentFromPackage);
}
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index ef0de18..704b425 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -106,6 +106,21 @@
}
@Override
+ public SparseIntArray getNonDefaultPackageModes(String packageName, int userId) {
+ synchronized (mLock) {
+ ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId);
+ if (packageModes == null) {
+ return new SparseIntArray();
+ }
+ SparseIntArray opModes = packageModes.get(packageName);
+ if (opModes == null) {
+ return new SparseIntArray();
+ }
+ return opModes.clone();
+ }
+ }
+
+ @Override
public int getUidMode(int uid, int op) {
synchronized (mLock) {
SparseIntArray opModes = mUidModes.get(uid, null);
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
index d8d0d48..9a564fc 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
@@ -39,6 +39,15 @@
SparseIntArray getNonDefaultUidModes(int uid);
/**
+ * Returns a copy of non-default app-ops with op as keys and their modes as values for a package
+ * and user.
+ * Returns an empty SparseIntArray if nothing is set.
+ * @param packageName for which we need the app-ops and their modes.
+ * @param userId for which the package is installed in.
+ */
+ SparseIntArray getNonDefaultPackageModes(String packageName, int userId);
+
+ /**
* Returns the app-op mode for a particular app-op of a uid.
* Returns default op mode if the op mode for particular uid and op is not set.
* @param uid user id for which we need the mode.
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java
index ac479b2..b8326ad 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java
@@ -46,6 +46,13 @@
}
@Override
+ public SparseIntArray getNonDefaultPackageModes(String packageName, int userId) {
+ Log.i(LOG_TAG, "getNonDefaultPackageModes("
+ + "packageName = " + packageName + ", userId = " + userId + ") ");
+ return mService.getNonDefaultPackageModes(packageName, userId);
+ }
+
+ @Override
public int getUidMode(int uid, int op) {
Log.i(LOG_TAG, "getUidMode(uid = " + uid + ", op = " + op + ")");
return mService.getUidMode(uid, op);
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index c50f2b7..af6470f 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -98,6 +98,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PermissionInfo;
+import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
import android.net.Uri;
@@ -162,6 +163,7 @@
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.component.ParsedAttribution;
import com.android.server.policy.AppOpsPolicy;
@@ -383,6 +385,9 @@
/** Package Manager internal. Access via {@link #getPackageManagerInternal()} */
private @Nullable PackageManagerInternal mPackageManagerInternal;
+ /** User Manager internal. Access via {@link #getUserManagerInternal()} */
+ private @Nullable UserManagerInternal mUserManagerInternal;
+
/** Interface for app-op modes.*/
@VisibleForTesting
AppOpsCheckingServiceInterface mAppOpsCheckingService;
@@ -525,22 +530,6 @@
pkgOps = null;
}
- public boolean isDefault() {
- boolean areAllPackageModesDefault = true;
- if (pkgOps != null) {
- for (String packageName : pkgOps.keySet()) {
- if (!mAppOpsCheckingService.arePackageModesDefault(packageName,
- UserHandle.getUserId(uid))) {
- areAllPackageModesDefault = false;
- break;
- }
- }
- }
- return (pkgOps == null || pkgOps.isEmpty())
- && mAppOpsCheckingService.areUidModesDefault(uid)
- && areAllPackageModesDefault;
- }
-
// Functions for uid mode access and manipulation.
public SparseIntArray getNonDefaultUidModes() {
return mAppOpsCheckingService.getNonDefaultUidModes(uid);
@@ -1076,6 +1065,17 @@
synchronized (this) {
upgradeLocked(mVersionAtBoot);
}
+ initializeUidStates();
+
+ getUserManagerInternal().addUserLifecycleListener(
+ new UserManagerInternal.UserLifecycleListener() {
+ @Override
+ public void onUserCreated(UserInfo user, Object token) {
+ initializeUserUidStates(user.id);
+ }
+
+ // onUserRemoved handled by #removeUser
+ });
mConstants.startMonitoring(mContext.getContentResolver());
mHistoricalRegistry.systemReady(mContext.getContentResolver());
@@ -1203,6 +1203,49 @@
}
/**
+ * Initialize uid state objects for state contained in the checking service.
+ */
+ private void initializeUidStates() {
+ UserManagerInternal umi = getUserManagerInternal();
+ int[] userIds = umi.getUserIds();
+ synchronized (this) {
+ for (int i = 0; i < userIds.length; i++) {
+ int userId = userIds[i];
+ initializeUserUidStatesLocked(userId);
+ }
+ }
+ }
+
+ private void initializeUserUidStates(int userId) {
+ synchronized (this) {
+ initializeUserUidStatesLocked(userId);
+ }
+ }
+
+ private void initializeUserUidStatesLocked(int userId) {
+ ArrayMap<String, ? extends PackageStateInternal> packageStates =
+ getPackageManagerInternal().getPackageStates();
+ for (int j = 0; j < packageStates.size(); j++) {
+ PackageStateInternal packageState = packageStates.valueAt(j);
+ int uid = UserHandle.getUid(userId, packageState.getAppId());
+ UidState uidState = getUidStateLocked(uid, true);
+ if (uidState.pkgOps == null) {
+ uidState.pkgOps = new ArrayMap<>();
+ }
+ String packageName = packageStates.keyAt(j);
+ Ops ops = new Ops(packageName, uidState);
+ uidState.pkgOps.put(packageName, ops);
+
+ SparseIntArray packageModes =
+ mAppOpsCheckingService.getNonDefaultPackageModes(packageName, userId);
+ for (int k = 0; k < packageModes.size(); k++) {
+ int code = packageModes.get(k);
+ ops.put(code, new Op(uidState, packageName, code, uid));
+ }
+ }
+ }
+
+ /**
* Sets a policy for handling app ops.
*
* @param policy The policy.
@@ -1687,13 +1730,6 @@
pkgOps.remove(ops.packageName);
mAppOpsCheckingService.removePackage(ops.packageName,
UserHandle.getUserId(uidState.uid));
- if (pkgOps.isEmpty()) {
- uidState.pkgOps = null;
- }
- if (uidState.isDefault()) {
- uidState.clear();
- mUidStates.remove(uid);
- }
}
}
}
@@ -2147,10 +2183,6 @@
UserHandle.getUserId(uidState.uid));
}
}
- if (uidState.isDefault()) {
- uidState.clear();
- mUidStates.remove(uidState.uid);
- }
if (uidChanged) {
uidState.evalForegroundOps();
}
@@ -3588,6 +3620,20 @@
}
/**
+ * @return {@link UserManagerInternal}
+ */
+ private @NonNull UserManagerInternal getUserManagerInternal() {
+ if (mUserManagerInternal == null) {
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+ }
+ if (mUserManagerInternal == null) {
+ throw new IllegalStateException("UserManagerInternal not loaded");
+ }
+
+ return mUserManagerInternal;
+ }
+
+ /**
* Create a restriction description matching the properties of the package.
*
* @param pkg The package to create the restriction description for
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 88fc51a3..d888c81 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1462,7 +1462,7 @@
mNm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- mSoundDoseHelper.configureSafeMediaVolume(/*forced=*/true, TAG);
+ mSoundDoseHelper.configureSafeMedia(/*forced=*/true, TAG);
initA11yMonitoring();
@@ -2206,19 +2206,19 @@
AudioSystem.STREAM_ASSISTANT : AudioSystem.STREAM_MUSIC;
if (mIsSingleVolume) {
- mStreamVolumeAlias = STREAM_VOLUME_ALIAS_TELEVISION;
+ mStreamVolumeAlias = STREAM_VOLUME_ALIAS_TELEVISION.clone();
dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
} else if (mUseVolumeGroupAliases) {
- mStreamVolumeAlias = STREAM_VOLUME_ALIAS_NONE;
+ mStreamVolumeAlias = STREAM_VOLUME_ALIAS_NONE.clone();
dtmfStreamAlias = AudioSystem.STREAM_DTMF;
} else {
switch (mPlatformType) {
case AudioSystem.PLATFORM_VOICE:
- mStreamVolumeAlias = STREAM_VOLUME_ALIAS_VOICE;
+ mStreamVolumeAlias = STREAM_VOLUME_ALIAS_VOICE.clone();
dtmfStreamAlias = AudioSystem.STREAM_RING;
break;
default:
- mStreamVolumeAlias = STREAM_VOLUME_ALIAS_DEFAULT;
+ mStreamVolumeAlias = STREAM_VOLUME_ALIAS_DEFAULT.clone();
dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
}
if (!mNotifAliasRing) {
@@ -10236,7 +10236,7 @@
// reading new configuration "safely" (i.e. under try catch) in case anything
// goes wrong.
Configuration config = context.getResources().getConfiguration();
- mSoundDoseHelper.configureSafeMediaVolume(/*forced*/false, TAG);
+ mSoundDoseHelper.configureSafeMedia(/*forced*/false, TAG);
boolean cameraSoundForced = readCameraSoundForced();
synchronized (mSettingsLock) {
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 42031c6..ac02eb7 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -38,6 +38,7 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.Log;
import android.util.MathUtils;
@@ -55,6 +56,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* Safe media volume management.
@@ -77,14 +79,16 @@
// SAFE_MEDIA_VOLUME_DISABLED according to country option. If not SAFE_MEDIA_VOLUME_DISABLED, it
// can be set to SAFE_MEDIA_VOLUME_INACTIVE by calling AudioService.disableSafeMediaVolume()
// (when user opts out).
+ // Note: when CSD calculation is enabled the state is set to SAFE_MEDIA_VOLUME_DISABLED
private static final int SAFE_MEDIA_VOLUME_NOT_CONFIGURED = 0;
private static final int SAFE_MEDIA_VOLUME_DISABLED = 1;
private static final int SAFE_MEDIA_VOLUME_INACTIVE = 2; // confirmed
private static final int SAFE_MEDIA_VOLUME_ACTIVE = 3; // unconfirmed
- private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = SAFE_MEDIA_VOLUME_MSG_START + 1;
+ private static final int MSG_CONFIGURE_SAFE_MEDIA = SAFE_MEDIA_VOLUME_MSG_START + 1;
private static final int MSG_PERSIST_SAFE_VOLUME_STATE = SAFE_MEDIA_VOLUME_MSG_START + 2;
private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = SAFE_MEDIA_VOLUME_MSG_START + 3;
+ private static final int MSG_PERSIST_CSD_VALUES = SAFE_MEDIA_VOLUME_MSG_START + 4;
private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
@@ -100,14 +104,16 @@
private static final int CSD_WARNING_TIMEOUT_MS_ACCUMULATION_START = -1;
private static final int CSD_WARNING_TIMEOUT_MS_MOMENTARY_EXPOSURE = 5000;
+ private static final String PERSIST_CSD_RECORD_FIELD_SEPARATOR = ",";
+ private static final String PERSIST_CSD_RECORD_SEPARATOR_CHAR = "|";
+ private static final String PERSIST_CSD_RECORD_SEPARATOR = "\\|";
+
private final EventLogger mLogger = new EventLogger(AudioService.LOG_NB_EVENTS_SOUND_DOSE,
"CSD updates");
private int mMcc = 0;
- private final boolean mEnableCsd;
-
- final Object mSafeMediaVolumeStateLock = new Object();
+ private final Object mSafeMediaVolumeStateLock = new Object();
private int mSafeMediaVolumeState;
// Used when safe volume warning message display is requested by setStreamVolume(). In this
@@ -148,10 +154,18 @@
@NonNull private final AudioHandler mAudioHandler;
@NonNull private final ISafeHearingVolumeController mVolumeController;
+ private final boolean mEnableCsd;
+
private ISoundDose mSoundDose;
+
+ private final Object mCsdStateLock = new Object();
+
+ @GuardedBy("mCsdStateLock")
private float mCurrentCsd = 0.f;
// dose at which the next dose reached warning occurs
+ @GuardedBy("mCsdStateLock")
private float mNextCsdWarning = 1.0f;
+ @GuardedBy("mCsdStateLock")
private final List<SoundDoseRecord> mDoseRecords = new ArrayList<>();
private final Context mContext;
@@ -169,11 +183,16 @@
}
public void onNewCsdValue(float currentCsd, SoundDoseRecord[] records) {
+ if (!mEnableCsd) {
+ Log.w(TAG, "onNewCsdValue: csd not supported, ignoring value");
+ return;
+ }
+
Log.i(TAG, "onNewCsdValue: " + currentCsd);
- if (mCurrentCsd < currentCsd) {
- // dose increase: going over next threshold?
- if ((mCurrentCsd < mNextCsdWarning) && (currentCsd >= mNextCsdWarning)) {
- if (mEnableCsd) {
+ synchronized (mCsdStateLock) {
+ if (mCurrentCsd < currentCsd) {
+ // dose increase: going over next threshold?
+ if ((mCurrentCsd < mNextCsdWarning) && (currentCsd >= mNextCsdWarning)) {
if (mNextCsdWarning == 5.0f) {
// 500% dose repeat
mVolumeController.postDisplayCsdWarning(
@@ -188,17 +207,18 @@
getTimeoutMsForWarning(
AudioManager.CSD_WARNING_DOSE_REACHED_1X));
}
+ mNextCsdWarning += 1.0f;
}
- mNextCsdWarning += 1.0f;
+ } else {
+ // dose decrease: dropping below previous threshold of warning?
+ if ((currentCsd < mNextCsdWarning - 1.0f) && (
+ mNextCsdWarning >= 2.0f)) {
+ mNextCsdWarning -= 1.0f;
+ }
}
- } else {
- // dose decrease: dropping below previous threshold of warning?
- if ((currentCsd < mNextCsdWarning - 1.0f) && (mNextCsdWarning >= 2.0f)) {
- mNextCsdWarning -= 1.0f;
- }
+ mCurrentCsd = currentCsd;
+ updateSoundDoseRecords_l(records, currentCsd);
}
- mCurrentCsd = currentCsd;
- updateSoundDoseRecords(records, currentCsd);
}
};
@@ -213,20 +233,22 @@
mContext = context;
- mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(),
- Settings.Global.AUDIO_SAFE_VOLUME_STATE, 0);
-
mEnableCsd = mContext.getResources().getBoolean(R.bool.config_audio_csd_enabled_default);
+ if (mEnableCsd) {
+ mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
+ } else {
+ mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(),
+ Settings.Global.AUDIO_SAFE_VOLUME_STATE, 0);
+ }
// The default safe volume index read here will be replaced by the actual value when
- // the mcc is read by onConfigureSafeVolume()
+ // the mcc is read by onConfigureSafeMedia()
+ // For now we use the same index for RS2 initial warning with CSD
mSafeMediaVolumeIndex = mContext.getResources().getInteger(
R.integer.config_safe_media_volume_index) * 10;
mAlarmManager = (AlarmManager) mContext.getSystemService(
Context.ALARM_SERVICE);
-
- initCsd();
}
float getRs2Value() {
@@ -456,8 +478,8 @@
}
}
- /*package*/ void configureSafeMediaVolume(boolean forced, String caller) {
- int msg = MSG_CONFIGURE_SAFE_MEDIA_VOLUME;
+ /*package*/ void configureSafeMedia(boolean forced, String caller) {
+ int msg = MSG_CONFIGURE_SAFE_MEDIA;
mAudioHandler.removeMessages(msg);
long time = 0;
@@ -507,8 +529,8 @@
/*package*/ void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_CONFIGURE_SAFE_MEDIA_VOLUME:
- onConfigureSafeVolume((msg.arg1 == 1), (String) msg.obj);
+ case MSG_CONFIGURE_SAFE_MEDIA:
+ onConfigureSafeMedia((msg.arg1 == 1), (String) msg.obj);
break;
case MSG_PERSIST_SAFE_VOLUME_STATE:
onPersistSafeVolumeState(msg.arg1);
@@ -519,8 +541,13 @@
Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, musicActiveMs,
UserHandle.USER_CURRENT);
break;
+ case MSG_PERSIST_CSD_VALUES:
+ onPersistSoundDoseRecords();
+ break;
+ default:
+ Log.e(TAG, "Unexpected msg to handle: " + msg.what);
+ break;
}
-
}
/*package*/ void dump(PrintWriter pw) {
@@ -540,33 +567,44 @@
/*package*/void reset() {
Log.d(TAG, "Reset the sound dose helper");
- initCsd();
- }
-
- private void initCsd() {
- synchronized (mSafeMediaVolumeStateLock) {
- if (mEnableCsd) {
- Log.v(TAG, "Initializing sound dose");
-
- mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
- mSoundDose = AudioSystem.getSoundDoseInterface(mSoundDoseCallback);
- try {
- if (mSoundDose != null && mSoundDose.asBinder().isBinderAlive()) {
- if (mCurrentCsd != 0.f) {
- Log.d(TAG, "Resetting the saved sound dose value " + mCurrentCsd);
- SoundDoseRecord[] records = mDoseRecords.toArray(
- new SoundDoseRecord[0]);
- mSoundDose.resetCsd(mCurrentCsd, records);
- }
+ mSoundDose = AudioSystem.getSoundDoseInterface(mSoundDoseCallback);
+ synchronized (mCsdStateLock) {
+ try {
+ if (mSoundDose != null && mSoundDose.asBinder().isBinderAlive()) {
+ if (mCurrentCsd != 0.f) {
+ Log.d(TAG,
+ "Resetting the saved sound dose value " + mCurrentCsd);
+ SoundDoseRecord[] records = mDoseRecords.toArray(
+ new SoundDoseRecord[0]);
+ mSoundDose.resetCsd(mCurrentCsd, records);
}
- } catch (RemoteException e) {
- // noop
}
+ } catch (RemoteException e) {
+ // noop
}
}
}
- private void onConfigureSafeVolume(boolean force, String caller) {
+ private void initCsd() {
+ if (mEnableCsd) {
+ Log.v(TAG, "Initializing sound dose");
+
+ synchronized (mCsdStateLock) {
+ // Restore persisted values
+ mCurrentCsd = parseGlobalSettingFloat(
+ Settings.Global.AUDIO_SAFE_CSD_CURRENT_VALUE, /* defaultValue= */0.f);
+ mNextCsdWarning = parseGlobalSettingFloat(
+ Settings.Global.AUDIO_SAFE_CSD_NEXT_WARNING, /* defaultValue= */1.f);
+ mDoseRecords.addAll(persistedStringToRecordList(
+ mSettings.getGlobalString(mAudioService.getContentResolver(),
+ Settings.Global.AUDIO_SAFE_CSD_DOSE_RECORDS)));
+ }
+
+ reset();
+ }
+ }
+
+ private void onConfigureSafeMedia(boolean force, String caller) {
synchronized (mSafeMediaVolumeStateLock) {
int mcc = mContext.getResources().getConfiguration().mcc;
if ((mMcc != mcc) || ((mMcc == 0) && force)) {
@@ -612,6 +650,10 @@
/*obj=*/null), /*delay=*/0);
}
}
+
+ if (mEnableCsd) {
+ initCsd();
+ }
}
private int getTimeoutMsForWarning(@AudioManager.CsdWarning int csdWarning) {
@@ -702,7 +744,8 @@
return null;
}
- private void updateSoundDoseRecords(SoundDoseRecord[] newRecords, float currentCsd) {
+ @GuardedBy("mCsdStateLock")
+ private void updateSoundDoseRecords_l(SoundDoseRecord[] newRecords, float currentCsd) {
long totalDuration = 0;
for (SoundDoseRecord record : newRecords) {
Log.i(TAG, " new record: " + record);
@@ -722,9 +765,87 @@
}
}
+ mAudioHandler.sendMessageAtTime(mAudioHandler.obtainMessage(MSG_PERSIST_CSD_VALUES,
+ /* arg1= */0, /* arg2= */0, /* obj= */null), /* delay= */0);
+
mLogger.enqueue(SoundDoseEvent.getDoseUpdateEvent(currentCsd, totalDuration));
}
+ private void onPersistSoundDoseRecords() {
+ synchronized (mCsdStateLock) {
+ mSettings.putGlobalString(mAudioService.getContentResolver(),
+ Settings.Global.AUDIO_SAFE_CSD_CURRENT_VALUE,
+ Float.toString(mCurrentCsd));
+ mSettings.putGlobalString(mAudioService.getContentResolver(),
+ Settings.Global.AUDIO_SAFE_CSD_NEXT_WARNING,
+ Float.toString(mNextCsdWarning));
+ mSettings.putGlobalString(mAudioService.getContentResolver(),
+ Settings.Global.AUDIO_SAFE_CSD_DOSE_RECORDS,
+ mDoseRecords.stream().map(
+ SoundDoseHelper::recordToPersistedString).collect(
+ Collectors.joining(PERSIST_CSD_RECORD_SEPARATOR_CHAR)));
+ }
+ }
+
+ private static String recordToPersistedString(SoundDoseRecord record) {
+ return record.timestamp
+ + PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.duration
+ + PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.value
+ + PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.averageMel;
+ }
+
+ private static List<SoundDoseRecord> persistedStringToRecordList(String records) {
+ if (records == null || records.isEmpty()) {
+ return null;
+ }
+ return Arrays.stream(TextUtils.split(records, PERSIST_CSD_RECORD_SEPARATOR)).map(
+ SoundDoseHelper::persistedStringToRecord).filter(Objects::nonNull).collect(
+ Collectors.toList());
+ }
+
+ private static SoundDoseRecord persistedStringToRecord(String record) {
+ if (record == null || record.isEmpty()) {
+ return null;
+ }
+ final String[] fields = TextUtils.split(record, PERSIST_CSD_RECORD_FIELD_SEPARATOR);
+ if (fields.length != 4) {
+ Log.w(TAG, "Expecting 4 fields for a SoundDoseRecord, parsed " + fields.length);
+ return null;
+ }
+
+ final SoundDoseRecord sdRecord = new SoundDoseRecord();
+ try {
+ sdRecord.timestamp = Long.parseLong(fields[0]);
+ sdRecord.duration = Integer.parseInt(fields[1]);
+ sdRecord.value = Float.parseFloat(fields[2]);
+ sdRecord.averageMel = Float.parseFloat(fields[3]);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Unable to parse persisted SoundDoseRecord: " + record, e);
+ return null;
+ }
+
+ return sdRecord;
+ }
+
+ private float parseGlobalSettingFloat(String audioSafeCsdCurrentValue, float defaultValue) {
+ String stringValue = mSettings.getGlobalString(mAudioService.getContentResolver(),
+ audioSafeCsdCurrentValue);
+ if (stringValue == null || stringValue.isEmpty()) {
+ Log.v(TAG, "No value stored in settings " + audioSafeCsdCurrentValue);
+ return defaultValue;
+ }
+
+ float value;
+ try {
+ value = Float.parseFloat(stringValue);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Error parsing float from settings " + audioSafeCsdCurrentValue, e);
+ value = defaultValue;
+ }
+
+ return value;
+ }
+
// StreamVolumeCommand contains the information needed to defer the process of
// setStreamVolume() in case the user has to acknowledge the safe volume warning message.
private static class StreamVolumeCommand {
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index 35df3ee..c0ea561 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -86,7 +86,8 @@
private static final Set<String> sEligibleForMultiUser = Sets.newArraySet(
PERMISSION_HELPER, NOTIFICATION_HELPER, SYNC_SETTINGS_HELPER, APP_LOCALES_HELPER,
- ACCOUNT_MANAGER_HELPER, USAGE_STATS_HELPER, PREFERRED_HELPER);
+ ACCOUNT_MANAGER_HELPER, USAGE_STATS_HELPER, PREFERRED_HELPER, SHORTCUT_MANAGER_HELPER
+ );
private int mUserId = UserHandle.USER_SYSTEM;
@@ -101,7 +102,7 @@
addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(mUserId));
addHelper(PERMISSION_HELPER, new PermissionBackupHelper(mUserId));
addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(mUserId));
- addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
+ addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper(mUserId));
addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper(mUserId));
addHelper(SLICES_HELPER, new SliceBackupHelper(this));
addHelper(PEOPLE_HELPER, new PeopleBackupHelper(mUserId));
diff --git a/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java b/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java
index dce1c96..39c649b 100644
--- a/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java
+++ b/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java
@@ -111,7 +111,7 @@
bb.order(ByteOrder.LITTLE_ENDIAN);
final int msgLen = bb.getInt();
- if (msgLen <= 0 || msgLen > MAX_CLIPBOARD_BYTES) {
+ if (msgLen < 0 || msgLen > MAX_CLIPBOARD_BYTES) {
throw new ProtocolException("Clipboard message length: " + msgLen + " out of bounds.");
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 064cd2d..ba96861 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -25,6 +25,7 @@
import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE;
import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE;
+import static com.android.server.devicestate.OverrideRequestController.FLAG_THERMAL_CRITICAL;
import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
import static com.android.server.devicestate.OverrideRequestController.STATUS_UNKNOWN;
@@ -183,6 +184,9 @@
ActivityTaskManagerInternal.ScreenObserver mOverrideRequestScreenObserver =
new OverrideRequestScreenObserver();
+ @NonNull
+ private final DeviceStateNotificationController mDeviceStateNotificationController;
+
public DeviceStateManagerService(@NonNull Context context) {
this(context, DeviceStatePolicy.Provider
.fromResources(context.getResources())
@@ -211,6 +215,13 @@
mDeviceStatePolicy.getDeviceStateProvider().setListener(mDeviceStateProviderListener);
mBinderService = new BinderService();
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+ mDeviceStateNotificationController = new DeviceStateNotificationController(
+ context, mHandler,
+ () -> {
+ synchronized (mLock) {
+ mActiveOverride.ifPresent(mOverrideRequestController::cancelRequest);
+ }
+ });
}
@Override
@@ -346,7 +357,8 @@
return mBinderService;
}
- private void updateSupportedStates(DeviceState[] supportedDeviceStates) {
+ private void updateSupportedStates(DeviceState[] supportedDeviceStates,
+ @DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
synchronized (mLock) {
final int[] oldStateIdentifiers = getSupportedStateIdentifiersLocked();
@@ -370,7 +382,7 @@
return;
}
- mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers);
+ mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers, reason);
updatePendingStateLocked();
setRearDisplayStateLocked();
@@ -596,7 +608,7 @@
@GuardedBy("mLock")
private void onOverrideRequestStatusChangedLocked(@NonNull OverrideRequest request,
- @OverrideRequestController.RequestStatus int status) {
+ @OverrideRequestController.RequestStatus int status, int flags) {
if (request.getRequestType() == OVERRIDE_REQUEST_TYPE_BASE_STATE) {
switch (status) {
case STATUS_ACTIVE:
@@ -616,10 +628,19 @@
switch (status) {
case STATUS_ACTIVE:
mActiveOverride = Optional.of(request);
+ mDeviceStateNotificationController.showStateActiveNotificationIfNeeded(
+ request.getRequestedState(), request.getUid());
break;
case STATUS_CANCELED:
if (mActiveOverride.isPresent() && mActiveOverride.get() == request) {
mActiveOverride = Optional.empty();
+ mDeviceStateNotificationController.cancelNotification(
+ request.getRequestedState());
+ if ((flags & FLAG_THERMAL_CRITICAL) == FLAG_THERMAL_CRITICAL) {
+ mDeviceStateNotificationController
+ .showThermalCriticalNotificationIfNeeded(
+ request.getRequestedState());
+ }
}
break;
case STATUS_UNKNOWN: // same as default
@@ -700,7 +721,7 @@
}
}
- private void requestStateInternal(int state, int flags, int callingPid,
+ private void requestStateInternal(int state, int flags, int callingPid, int callingUid,
@NonNull IBinder token, boolean hasControlDeviceStatePermission) {
synchronized (mLock) {
final ProcessRecord processRecord = mProcessRecords.get(callingPid);
@@ -721,8 +742,8 @@
+ " is not supported.");
}
- OverrideRequest request = new OverrideRequest(token, callingPid, state, flags,
- OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+ OverrideRequest request = new OverrideRequest(token, callingPid, callingUid,
+ state, flags, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
// If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay
if (!hasControlDeviceStatePermission && mRearDisplayState != null
@@ -762,7 +783,7 @@
}
private void requestBaseStateOverrideInternal(int state, int flags, int callingPid,
- @NonNull IBinder token) {
+ int callingUid, @NonNull IBinder token) {
synchronized (mLock) {
final Optional<DeviceState> deviceState = getStateLocked(state);
if (!deviceState.isPresent()) {
@@ -782,8 +803,8 @@
+ " token: " + token);
}
- OverrideRequest request = new OverrideRequest(token, callingPid, state, flags,
- OVERRIDE_REQUEST_TYPE_BASE_STATE);
+ OverrideRequest request = new OverrideRequest(token, callingPid, callingUid,
+ state, flags, OVERRIDE_REQUEST_TYPE_BASE_STATE);
mOverrideRequestController.addBaseStateRequest(request);
}
}
@@ -953,11 +974,12 @@
@IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int mCurrentBaseState;
@Override
- public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates) {
+ public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates,
+ @DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
if (newDeviceStates.length == 0) {
throw new IllegalArgumentException("Supported device states must not be empty");
}
- updateSupportedStates(newDeviceStates);
+ updateSupportedStates(newDeviceStates, reason);
}
@Override
@@ -1085,6 +1107,7 @@
@Override // Binder call
public void requestState(IBinder token, int state, int flags) {
final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
// Allow top processes to request a device state change
// If the calling process ID is not the top app, then we check if this process
// holds a permission to CONTROL_DEVICE_STATE
@@ -1099,7 +1122,8 @@
final long callingIdentity = Binder.clearCallingIdentity();
try {
- requestStateInternal(state, flags, callingPid, token, hasControlStatePermission);
+ requestStateInternal(state, flags, callingPid, callingUid, token,
+ hasControlStatePermission);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
@@ -1124,6 +1148,7 @@
@Override // Binder call
public void requestBaseStateOverride(IBinder token, int state, int flags) {
final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
"Permission required to control base state of device.");
@@ -1133,7 +1158,7 @@
final long callingIdentity = Binder.clearCallingIdentity();
try {
- requestBaseStateOverrideInternal(state, flags, callingPid, token);
+ requestBaseStateOverrideInternal(state, flags, callingPid, callingUid, token);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
new file mode 100644
index 0000000..2f14998
--- /dev/null
+++ b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
@@ -0,0 +1,293 @@
+/*
+ * 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.devicestate;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.hardware.devicestate.DeviceStateManager;
+import android.os.Handler;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Manages the user-visible device state notifications.
+ */
+class DeviceStateNotificationController extends BroadcastReceiver {
+ private static final String TAG = "DeviceStateNotificationController";
+
+ @VisibleForTesting static final String INTENT_ACTION_CANCEL_STATE =
+ "com.android.server.devicestate.INTENT_ACTION_CANCEL_STATE";
+ @VisibleForTesting static final int NOTIFICATION_ID = 1;
+ @VisibleForTesting static final String CHANNEL_ID = "DeviceStateManager";
+ @VisibleForTesting static final String NOTIFICATION_TAG = "DeviceStateManager";
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final NotificationManager mNotificationManager;
+ private final PackageManager mPackageManager;
+
+ // Stores the notification title and content indexed with the device state identifier.
+ private final SparseArray<NotificationInfo> mNotificationInfos;
+
+ // The callback when a device state is requested to be canceled.
+ private final Runnable mCancelStateRunnable;
+
+ DeviceStateNotificationController(@NonNull Context context, @NonNull Handler handler,
+ @NonNull Runnable cancelStateRunnable) {
+ this(context, handler, cancelStateRunnable, getNotificationInfos(context),
+ context.getPackageManager(), context.getSystemService(NotificationManager.class));
+ }
+
+ @VisibleForTesting
+ DeviceStateNotificationController(
+ @NonNull Context context, @NonNull Handler handler,
+ @NonNull Runnable cancelStateRunnable,
+ @NonNull SparseArray<NotificationInfo> notificationInfos,
+ @NonNull PackageManager packageManager,
+ @NonNull NotificationManager notificationManager) {
+ mContext = context;
+ mHandler = handler;
+ mCancelStateRunnable = cancelStateRunnable;
+ mNotificationInfos = notificationInfos;
+ mPackageManager = packageManager;
+ mNotificationManager = notificationManager;
+ mContext.registerReceiver(
+ this,
+ new IntentFilter(INTENT_ACTION_CANCEL_STATE),
+ android.Manifest.permission.CONTROL_DEVICE_STATE,
+ mHandler,
+ Context.RECEIVER_NOT_EXPORTED);
+ }
+
+ /**
+ * Displays the ongoing notification indicating that the device state is active. Does nothing if
+ * the state does not have an active notification.
+ *
+ * @param state the active device state identifier.
+ * @param requestingAppUid the uid of the requesting app used to retrieve the app name.
+ */
+ void showStateActiveNotificationIfNeeded(int state, int requestingAppUid) {
+ NotificationInfo info = mNotificationInfos.get(state);
+ if (info == null || !info.hasActiveNotification()) {
+ return;
+ }
+ String requesterApplicationLabel = getApplicationLabel(requestingAppUid);
+ if (requesterApplicationLabel != null) {
+ showNotification(
+ info.name, info.activeNotificationTitle,
+ String.format(info.activeNotificationContent, requesterApplicationLabel),
+ true /* ongoing */
+ );
+ } else {
+ Slog.e(TAG, "Cannot determine the requesting app name when showing state active "
+ + "notification. uid=" + requestingAppUid + ", state=" + state);
+ }
+ }
+
+ /**
+ * Displays the notification indicating that the device state is canceled due to thermal
+ * critical condition. Does nothing if the state does not have a thermal critical notification.
+ *
+ * @param state the identifier of the device state being canceled.
+ */
+ void showThermalCriticalNotificationIfNeeded(int state) {
+ NotificationInfo info = mNotificationInfos.get(state);
+ if (info == null || !info.hasThermalCriticalNotification()) {
+ return;
+ }
+ showNotification(
+ info.name, info.thermalCriticalNotificationTitle,
+ info.thermalCriticalNotificationContent, false /* ongoing */
+ );
+ }
+
+ /**
+ * Cancels the notification of the corresponding device state.
+ *
+ * @param state the device state identifier.
+ */
+ void cancelNotification(int state) {
+ if (!mNotificationInfos.contains(state)) {
+ return;
+ }
+ mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
+ }
+
+ @Override
+ public void onReceive(@NonNull Context context, @Nullable Intent intent) {
+ if (intent != null) {
+ if (INTENT_ACTION_CANCEL_STATE.equals(intent.getAction())) {
+ mCancelStateRunnable.run();
+ }
+ }
+ }
+
+ /**
+ * Displays a notification with the specified name, title, and content.
+ *
+ * @param name the name of the notification.
+ * @param title the title of the notification.
+ * @param content the content of the notification.
+ * @param ongoing if true, display an ongoing (sticky) notification with a turn off button.
+ */
+ private void showNotification(
+ @NonNull String name, @NonNull String title, @NonNull String content, boolean ongoing) {
+ final NotificationChannel channel = new NotificationChannel(
+ CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH);
+ final Notification.Builder builder = new Notification.Builder(mContext, CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_lock) // TODO(b/266833171) update icons when available.
+ .setContentTitle(title)
+ .setContentText(content)
+ .setSubText(name)
+ .setLocalOnly(true)
+ .setOngoing(ongoing)
+ .setCategory(Notification.CATEGORY_SYSTEM);
+
+ if (ongoing) {
+ final Intent intent = new Intent(INTENT_ACTION_CANCEL_STATE)
+ .setPackage(mContext.getPackageName());
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE);
+ final Notification.Action action = new Notification.Action.Builder(
+ null /* icon */,
+ mContext.getString(R.string.device_state_notification_turn_off_button),
+ pendingIntent)
+ .build();
+ builder.addAction(action);
+ }
+
+ mNotificationManager.createNotificationChannel(channel);
+ mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build());
+ }
+
+ /**
+ * Loads the resources for the notifications. The device state identifiers and strings are
+ * stored in arrays. All the string arrays must have the same length and same order as the
+ * identifier array.
+ */
+ private static SparseArray<NotificationInfo> getNotificationInfos(Context context) {
+ final SparseArray<NotificationInfo> notificationInfos = new SparseArray<>();
+
+ final int[] stateIdentifiers =
+ context.getResources().getIntArray(
+ R.array.device_state_notification_state_identifiers);
+ final String[] names =
+ context.getResources().getStringArray(R.array.device_state_notification_names);
+ final String[] activeNotificationTitles =
+ context.getResources().getStringArray(
+ R.array.device_state_notification_active_titles);
+ final String[] activeNotificationContents =
+ context.getResources().getStringArray(
+ R.array.device_state_notification_active_contents);
+ final String[] thermalCriticalNotificationTitles =
+ context.getResources().getStringArray(
+ R.array.device_state_notification_thermal_titles);
+ final String[] thermalCriticalNotificationContents =
+ context.getResources().getStringArray(
+ R.array.device_state_notification_thermal_contents);
+
+ if (stateIdentifiers.length != names.length
+ || stateIdentifiers.length != activeNotificationTitles.length
+ || stateIdentifiers.length != activeNotificationContents.length
+ || stateIdentifiers.length != thermalCriticalNotificationTitles.length
+ || stateIdentifiers.length != thermalCriticalNotificationContents.length
+ ) {
+ throw new IllegalStateException(
+ "The length of state identifiers and notification texts must match!");
+ }
+
+ for (int i = 0; i < stateIdentifiers.length; i++) {
+ int identifier = stateIdentifiers[i];
+ if (identifier == DeviceStateManager.INVALID_DEVICE_STATE) {
+ continue;
+ }
+
+ notificationInfos.put(
+ identifier,
+ new NotificationInfo(
+ names[i], activeNotificationTitles[i], activeNotificationContents[i],
+ thermalCriticalNotificationTitles[i],
+ thermalCriticalNotificationContents[i])
+ );
+ }
+
+ return notificationInfos;
+ }
+
+ /**
+ * A helper function to get app name (label) using the app uid.
+ *
+ * @param uid the uid of the app.
+ * @return app name (label) if found, or null otherwise.
+ */
+ @Nullable
+ private String getApplicationLabel(int uid) {
+ String packageName = mPackageManager.getNameForUid(uid);
+ try {
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
+ packageName, PackageManager.ApplicationInfoFlags.of(0));
+ return appInfo.loadLabel(mPackageManager).toString();
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * A data class storing string resources of the notification of a device state.
+ */
+ @VisibleForTesting
+ static class NotificationInfo {
+ public final String name;
+ public final String activeNotificationTitle;
+ public final String activeNotificationContent;
+ public final String thermalCriticalNotificationTitle;
+ public final String thermalCriticalNotificationContent;
+
+ NotificationInfo(String name, String activeNotificationTitle,
+ String activeNotificationContent, String thermalCriticalNotificationTitle,
+ String thermalCriticalNotificationContent) {
+
+ this.name = name;
+ this.activeNotificationTitle = activeNotificationTitle;
+ this.activeNotificationContent = activeNotificationContent;
+ this.thermalCriticalNotificationTitle = thermalCriticalNotificationTitle;
+ this.thermalCriticalNotificationContent = thermalCriticalNotificationContent;
+ }
+
+ boolean hasActiveNotification() {
+ return activeNotificationTitle != null && activeNotificationTitle.length() > 0;
+ }
+
+ boolean hasThermalCriticalNotification() {
+ return thermalCriticalNotificationTitle != null
+ && thermalCriticalNotificationTitle.length() > 0;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index 109bf63..fecc13f 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -19,8 +19,12 @@
import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
+import android.annotation.IntDef;
import android.annotation.IntRange;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Responsible for providing the set of supported {@link DeviceState device states} as well as the
* current device state.
@@ -28,12 +32,42 @@
* @see DeviceStatePolicy
*/
public interface DeviceStateProvider {
+ int SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT = 0;
+
+ /**
+ * Indicating that the supported device states changed callback is trigger for initial listener
+ * registration.
+ */
+ int SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED = 1;
+
+ /**
+ * Indicating that the supported device states have changed because the thermal condition
+ * returned to normal status from critical status.
+ */
+ int SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL = 2;
+
+ /**
+ * Indicating that the supported device states have changed because of thermal critical
+ * condition.
+ */
+ int SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL = 3;
+
+ @IntDef(prefix = { "SUPPORTED_DEVICE_STATES_CHANGED_" }, value = {
+ SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT,
+ SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED,
+ SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL,
+ SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface SupportedStatesUpdatedReason {}
+
/**
* Registers a listener for changes in provider state.
* <p>
- * It is <b>required</b> that {@link Listener#onSupportedDeviceStatesChanged(DeviceState[])} be
- * called followed by {@link Listener#onStateChanged(int)} with the initial values on successful
- * registration of the listener.
+ * It is <b>required</b> that
+ * {@link Listener#onSupportedDeviceStatesChanged(DeviceState[], int)} be called followed by
+ * {@link Listener#onStateChanged(int)} with the initial values on successful registration of
+ * the listener.
*/
void setListener(Listener listener);
@@ -53,18 +87,20 @@
* to zero and there must always be at least one supported device state.
*
* @param newDeviceStates array of supported device states.
+ * @param reason the reason for the supported device states change.
*
* @throws IllegalArgumentException if the list of device states is empty or if one of the
* provided states contains an invalid identifier.
*/
- void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates);
+ void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates,
+ @SupportedStatesUpdatedReason int reason);
/**
* Called to notify the listener of a change in current device state. Required to be called
* once on successful registration of the listener and then once on every subsequent change
* in device state. Value must have been included in the set of supported device states
* provided in the most recent call to
- * {@link #onSupportedDeviceStatesChanged(DeviceState[])}.
+ * {@link #onSupportedDeviceStatesChanged(DeviceState[], int)}.
*
* @param identifier the identifier of the new device state.
*
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java
index 325e384..74cf184 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequest.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java
@@ -31,6 +31,7 @@
final class OverrideRequest {
private final IBinder mToken;
private final int mPid;
+ private final int mUid;
private final int mRequestedState;
@DeviceStateRequest.RequestFlags
private final int mFlags;
@@ -68,10 +69,11 @@
@Retention(RetentionPolicy.SOURCE)
public @interface OverrideRequestType {}
- OverrideRequest(IBinder token, int pid, int requestedState,
+ OverrideRequest(IBinder token, int pid, int uid, int requestedState,
@DeviceStateRequest.RequestFlags int flags, @OverrideRequestType int requestType) {
mToken = token;
mPid = pid;
+ mUid = uid;
mRequestedState = requestedState;
mFlags = flags;
mRequestType = requestType;
@@ -85,6 +87,10 @@
return mPid;
}
+ int getUid() {
+ return mUid;
+ }
+
int getRequestedState() {
return mRequestedState;
}
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
index e39c732..2ed4765 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -59,6 +59,11 @@
@Retention(RetentionPolicy.SOURCE)
@interface RequestStatus {}
+ /**
+ * A flag indicating that the status change was triggered by thermal critical status.
+ */
+ static final int FLAG_THERMAL_CRITICAL = 1 << 0;
+
static String statusToString(@RequestStatus int status) {
switch (status) {
case STATUS_ACTIVE:
@@ -106,7 +111,7 @@
void addRequest(@NonNull OverrideRequest request) {
OverrideRequest previousRequest = mRequest;
mRequest = request;
- mListener.onStatusChanged(request, STATUS_ACTIVE);
+ mListener.onStatusChanged(request, STATUS_ACTIVE, 0 /* flags */);
if (previousRequest != null) {
cancelRequestLocked(previousRequest);
@@ -116,7 +121,7 @@
void addBaseStateRequest(@NonNull OverrideRequest request) {
OverrideRequest previousRequest = mBaseStateRequest;
mBaseStateRequest = request;
- mListener.onStatusChanged(request, STATUS_ACTIVE);
+ mListener.onStatusChanged(request, STATUS_ACTIVE, 0 /* flags */);
if (previousRequest != null) {
cancelRequestLocked(previousRequest);
@@ -219,14 +224,17 @@
* Notifies the controller that the set of supported states has changed. The controller will
* notify the listener of all changes to request status as a result of this change.
*/
- void handleNewSupportedStates(int[] newSupportedStates) {
+ void handleNewSupportedStates(int[] newSupportedStates,
+ @DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
+ boolean isThermalCritical =
+ reason == DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL;
if (mBaseStateRequest != null && !contains(newSupportedStates,
mBaseStateRequest.getRequestedState())) {
- cancelCurrentBaseStateRequestLocked();
+ cancelCurrentBaseStateRequestLocked(isThermalCritical ? FLAG_THERMAL_CRITICAL : 0);
}
if (mRequest != null && !contains(newSupportedStates, mRequest.getRequestedState())) {
- cancelCurrentRequestLocked();
+ cancelCurrentRequestLocked(isThermalCritical ? FLAG_THERMAL_CRITICAL : 0);
}
}
@@ -244,7 +252,11 @@
}
private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel) {
- mListener.onStatusChanged(requestToCancel, STATUS_CANCELED);
+ cancelRequestLocked(requestToCancel, 0 /* flags */);
+ }
+
+ private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel, int flags) {
+ mListener.onStatusChanged(requestToCancel, STATUS_CANCELED, flags);
}
/**
@@ -252,12 +264,16 @@
* Notifies the listener of the canceled status as well.
*/
private void cancelCurrentRequestLocked() {
+ cancelCurrentRequestLocked(0 /* flags */);
+ }
+
+ private void cancelCurrentRequestLocked(int flags) {
if (mRequest == null) {
Slog.w(TAG, "Attempted to cancel a null OverrideRequest");
return;
}
mStickyRequest = false;
- cancelRequestLocked(mRequest);
+ cancelRequestLocked(mRequest, flags);
mRequest = null;
}
@@ -266,11 +282,15 @@
* Notifies the listener of the canceled status as well.
*/
private void cancelCurrentBaseStateRequestLocked() {
+ cancelCurrentBaseStateRequestLocked(0 /* flags */);
+ }
+
+ private void cancelCurrentBaseStateRequestLocked(int flags) {
if (mBaseStateRequest == null) {
Slog.w(TAG, "Attempted to cancel a null OverrideRequest");
return;
}
- cancelRequestLocked(mBaseStateRequest);
+ cancelRequestLocked(mBaseStateRequest, flags);
mBaseStateRequest = null;
}
@@ -284,12 +304,14 @@
}
public interface StatusChangeListener {
+
/**
* Notifies the listener of a change in request status. If a change within the controller
* causes one request to become active and one to become either suspended or cancelled, this
* method is guaranteed to be called with the active request first before the suspended or
* cancelled request.
*/
- void onStatusChanged(@NonNull OverrideRequest request, @RequestStatus int newStatus);
+ void onStatusChanged(@NonNull OverrideRequest request, @RequestStatus int newStatus,
+ int flags);
}
}
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index 188672a..a921a54 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -118,11 +118,13 @@
final int state = l.getState().intValue();
final Layout layout = createLayout(state);
for (com.android.server.display.config.layout.Display d: l.getDisplay()) {
+ assert layout != null;
Layout.Display display = layout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()),
d.isDefaultDisplay(),
d.isEnabled(),
- mIdProducer);
+ mIdProducer,
+ d.getBrightnessThrottlingMapId());
if (FRONT_STRING.equals(d.getPosition())) {
display.setPosition(POSITION_FRONT);
diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java
index fa9a100..f229d0f 100644
--- a/services/core/java/com/android/server/display/DisplayControl.java
+++ b/services/core/java/com/android/server/display/DisplayControl.java
@@ -28,7 +28,8 @@
* Calls into SurfaceFlinger for Display creation and deletion.
*/
public class DisplayControl {
- private static native IBinder nativeCreateDisplay(String name, boolean secure);
+ private static native IBinder nativeCreateDisplay(String name, boolean secure,
+ float requestedRefreshRate);
private static native void nativeDestroyDisplay(IBinder displayToken);
private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes);
private static native long[] nativeGetPhysicalDisplayIds();
@@ -46,7 +47,25 @@
*/
public static IBinder createDisplay(String name, boolean secure) {
Objects.requireNonNull(name, "name must not be null");
- return nativeCreateDisplay(name, secure);
+ return nativeCreateDisplay(name, secure, 0.0f);
+ }
+
+ /**
+ * Create a display in SurfaceFlinger.
+ *
+ * @param name The name of the display
+ * @param secure Whether this display is secure.
+ * @param requestedRefreshRate The requested refresh rate in frames per second.
+ * For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on
+ * 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded
+ * up or down to a divisor of the physical display. If 0 is specified, the virtual
+ * display is refreshed at the physical display refresh rate.
+ * @return The token reference for the display in SurfaceFlinger.
+ */
+ public static IBinder createDisplay(String name, boolean secure,
+ float requestedRefreshRate) {
+ Objects.requireNonNull(name, "name must not be null");
+ return nativeCreateDisplay(name, secure, requestedRefreshRate);
}
/**
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index e048a0f..2af995b 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -74,8 +74,10 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import javax.xml.datatype.DatatypeConfigurationException;
@@ -411,6 +413,8 @@
public static final String QUIRK_CAN_SET_BRIGHTNESS_VIA_HWC = "canSetBrightnessViaHwc";
+ static final String DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID = "default";
+
private static final float BRIGHTNESS_DEFAULT = 0.5f;
private static final String ETC_DIR = "etc";
private static final String DISPLAY_CONFIG_DIR = "displayconfig";
@@ -421,6 +425,8 @@
private static final String STABLE_ID_SUFFIX_FORMAT = "id_%d";
private static final String NO_SUFFIX_FORMAT = "%d";
private static final long STABLE_FLAG = 1L << 62;
+ private static final int DEFAULT_PEAK_REFRESH_RATE = 0;
+ private static final int DEFAULT_REFRESH_RATE = 60;
private static final int DEFAULT_LOW_REFRESH_RATE = 60;
private static final int DEFAULT_HIGH_REFRESH_RATE = 0;
private static final int[] DEFAULT_BRIGHTNESS_THRESHOLDS = new int[]{};
@@ -589,17 +595,29 @@
* using higher refresh rates, even if display modes with higher refresh rates are available
* from hardware composer. Only has an effect if the value is non-zero.
*/
- private int mDefaultHighRefreshRate = DEFAULT_HIGH_REFRESH_RATE;
+ private int mDefaultPeakRefreshRate = DEFAULT_PEAK_REFRESH_RATE;
/**
* The default refresh rate for a given device. This value sets the higher default
* refresh rate. If the hardware composer on the device supports display modes with
* a higher refresh rate than the default value specified here, the framework may use those
* higher refresh rate modes if an app chooses one by setting preferredDisplayModeId or calling
- * setFrameRate(). We have historically allowed fallback to mDefaultHighRefreshRate if
- * mDefaultLowRefreshRate is set to 0, but this is not supported anymore.
+ * setFrameRate(). We have historically allowed fallback to mDefaultPeakRefreshRate if
+ * mDefaultRefreshRate is set to 0, but this is not supported anymore.
*/
- private int mDefaultLowRefreshRate = DEFAULT_LOW_REFRESH_RATE;
+ private int mDefaultRefreshRate = DEFAULT_REFRESH_RATE;
+
+ /**
+ * Default refresh rate in the high zone defined by brightness and ambient thresholds.
+ * If non-positive, then the refresh rate is unchanged even if thresholds are configured.
+ */
+ private int mDefaultHighBlockingZoneRefreshRate = DEFAULT_HIGH_REFRESH_RATE;
+
+ /**
+ * Default refresh rate in the zone defined by brightness and ambient thresholds.
+ * If non-positive, then the refresh rate is unchanged even if thresholds are configured.
+ */
+ private int mDefaultLowBlockingZoneRefreshRate = DEFAULT_LOW_REFRESH_RATE;
/**
* The display uses different gamma curves for different refresh rates. It's hard for panel
@@ -627,13 +645,7 @@
private int[] mHighDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
private int[] mHighAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
- // Brightness Throttling data may be updated via the DeviceConfig. Here we store the original
- // data, which comes from the ddc, and the current one, which may be the DeviceConfig
- // overwritten value.
- private BrightnessThrottlingData mBrightnessThrottlingData;
- private BrightnessThrottlingData mOriginalBrightnessThrottlingData;
- // The concurrent displays mode might need a stricter throttling policy
- private BrightnessThrottlingData mConcurrentDisplaysBrightnessThrottlingData;
+ private Map<String, BrightnessThrottlingData> mBrightnessThrottlingDataMap = new HashMap();
@Nullable
private HostUsiVersion mHostUsiVersion;
@@ -779,10 +791,6 @@
return config;
}
- void setBrightnessThrottlingData(BrightnessThrottlingData brightnessThrottlingData) {
- mBrightnessThrottlingData = brightnessThrottlingData;
- }
-
/**
* Return the brightness mapping nits array.
*
@@ -1282,18 +1290,11 @@
}
/**
+ * @param id The ID of the throttling data
* @return brightness throttling configuration data for the display.
*/
- public BrightnessThrottlingData getBrightnessThrottlingData() {
- return BrightnessThrottlingData.create(mBrightnessThrottlingData);
- }
-
- /**
- * @return brightness throttling configuration data for the display for the concurrent
- * displays mode.
- */
- public BrightnessThrottlingData getConcurrentDisplaysBrightnessThrottlingData() {
- return BrightnessThrottlingData.create(mConcurrentDisplaysBrightnessThrottlingData);
+ public BrightnessThrottlingData getBrightnessThrottlingData(String id) {
+ return BrightnessThrottlingData.create(mBrightnessThrottlingDataMap.get(id));
}
/**
@@ -1327,15 +1328,29 @@
/**
* @return Default peak refresh rate of the associated display
*/
- public int getDefaultHighRefreshRate() {
- return mDefaultHighRefreshRate;
+ public int getDefaultPeakRefreshRate() {
+ return mDefaultPeakRefreshRate;
}
/**
* @return Default refresh rate of the associated display
*/
- public int getDefaultLowRefreshRate() {
- return mDefaultLowRefreshRate;
+ public int getDefaultRefreshRate() {
+ return mDefaultRefreshRate;
+ }
+
+ /**
+ * @return Default refresh rate in the higher blocking zone of the associated display
+ */
+ public int getDefaultHighBlockingZoneRefreshRate() {
+ return mDefaultHighBlockingZoneRefreshRate;
+ }
+
+ /**
+ * @return Default refresh rate in the lower blocking zone of the associated display
+ */
+ public int getDefaultLowBlockingZoneRefreshRate() {
+ return mDefaultLowBlockingZoneRefreshRate;
}
/**
@@ -1411,8 +1426,7 @@
+ ", isHbmEnabled=" + mIsHighBrightnessModeEnabled
+ ", mHbmData=" + mHbmData
+ ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline
- + ", mBrightnessThrottlingData=" + mBrightnessThrottlingData
- + ", mOriginalBrightnessThrottlingData=" + mOriginalBrightnessThrottlingData
+ + ", mBrightnessThrottlingData=" + mBrightnessThrottlingDataMap
+ "\n"
+ ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease
+ ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease
@@ -1482,8 +1496,10 @@
+ ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
+ ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
+ "\n"
- + ", mDefaultRefreshRate= " + mDefaultLowRefreshRate
- + ", mDefaultPeakRefreshRate= " + mDefaultHighRefreshRate
+ + ", mDefaultLowBlockingZoneRefreshRate= " + mDefaultLowBlockingZoneRefreshRate
+ + ", mDefaultHighBlockingZoneRefreshRate= " + mDefaultHighBlockingZoneRefreshRate
+ + ", mDefaultPeakRefreshRate= " + mDefaultPeakRefreshRate
+ + ", mDefaultRefreshRate= " + mDefaultRefreshRate
+ ", mLowDisplayBrightnessThresholds= "
+ Arrays.toString(mLowDisplayBrightnessThresholds)
+ ", mLowAmbientBrightnessThresholds= "
@@ -1545,8 +1561,7 @@
loadBrightnessDefaultFromDdcXml(config);
loadBrightnessConstraintsFromConfigXml();
loadBrightnessMap(config);
- loadBrightnessThrottlingMap(config);
- loadConcurrentDisplaysBrightnessThrottlingMap(config);
+ loadBrightnessThrottlingMaps(config);
loadHighBrightnessModeData(config);
loadQuirks(config);
loadBrightnessRamps(config);
@@ -1756,76 +1771,47 @@
return Spline.createSpline(nits, ratios);
}
- private void loadBrightnessThrottlingMap(DisplayConfiguration config) {
+ private void loadBrightnessThrottlingMaps(DisplayConfiguration config) {
final ThermalThrottling throttlingConfig = config.getThermalThrottling();
if (throttlingConfig == null) {
Slog.i(TAG, "No thermal throttling config found");
return;
}
- final BrightnessThrottlingMap map = throttlingConfig.getBrightnessThrottlingMap();
- if (map == null) {
+ final List<BrightnessThrottlingMap> maps = throttlingConfig.getBrightnessThrottlingMap();
+ if (maps == null || maps.isEmpty()) {
Slog.i(TAG, "No brightness throttling map found");
return;
}
- final List<BrightnessThrottlingPoint> points = map.getBrightnessThrottlingPoint();
- // At least 1 point is guaranteed by the display device config schema
- List<BrightnessThrottlingData.ThrottlingLevel> throttlingLevels =
- new ArrayList<>(points.size());
+ for (BrightnessThrottlingMap map : maps) {
+ final List<BrightnessThrottlingPoint> points = map.getBrightnessThrottlingPoint();
+ // At least 1 point is guaranteed by the display device config schema
+ List<BrightnessThrottlingData.ThrottlingLevel> throttlingLevels =
+ new ArrayList<>(points.size());
- boolean badConfig = false;
- for (BrightnessThrottlingPoint point : points) {
- ThermalStatus status = point.getThermalStatus();
- if (!thermalStatusIsValid(status)) {
- badConfig = true;
- break;
+ boolean badConfig = false;
+ for (BrightnessThrottlingPoint point : points) {
+ ThermalStatus status = point.getThermalStatus();
+ if (!thermalStatusIsValid(status)) {
+ badConfig = true;
+ break;
+ }
+
+ throttlingLevels.add(new BrightnessThrottlingData.ThrottlingLevel(
+ convertThermalStatus(status), point.getBrightness().floatValue()));
}
- throttlingLevels.add(new BrightnessThrottlingData.ThrottlingLevel(
- convertThermalStatus(status), point.getBrightness().floatValue()));
- }
-
- if (!badConfig) {
- mBrightnessThrottlingData = BrightnessThrottlingData.create(throttlingLevels);
- mOriginalBrightnessThrottlingData = mBrightnessThrottlingData;
- }
- }
-
- private void loadConcurrentDisplaysBrightnessThrottlingMap(DisplayConfiguration config) {
- final ThermalThrottling throttlingConfig = config.getThermalThrottling();
- if (throttlingConfig == null) {
- Slog.i(TAG, "No concurrent displays thermal throttling config found");
- return;
- }
-
- final BrightnessThrottlingMap map =
- throttlingConfig.getConcurrentDisplaysBrightnessThrottlingMap();
- if (map == null) {
- Slog.i(TAG, "No concurrent displays brightness throttling map found");
- return;
- }
-
- final List<BrightnessThrottlingPoint> points = map.getBrightnessThrottlingPoint();
- // At least 1 point is guaranteed by the display device config schema
- List<BrightnessThrottlingData.ThrottlingLevel> throttlingLevels =
- new ArrayList<>(points.size());
-
- boolean badConfig = false;
- for (BrightnessThrottlingPoint point : points) {
- ThermalStatus status = point.getThermalStatus();
- if (!thermalStatusIsValid(status)) {
- badConfig = true;
- break;
+ if (!badConfig) {
+ String id = map.getId() == null ? DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID
+ : map.getId();
+ if (mBrightnessThrottlingDataMap.containsKey(id)) {
+ throw new RuntimeException("Brightness throttling data with ID " + id
+ + " already exists");
+ }
+ mBrightnessThrottlingDataMap.put(id,
+ BrightnessThrottlingData.create(throttlingLevels));
}
-
- throttlingLevels.add(new BrightnessThrottlingData.ThrottlingLevel(
- convertThermalStatus(status), point.getBrightness().floatValue()));
- }
-
- if (!badConfig) {
- mConcurrentDisplaysBrightnessThrottlingData =
- BrightnessThrottlingData.create(throttlingLevels);
}
}
@@ -1838,10 +1824,31 @@
BlockingZoneConfig higherBlockingZoneConfig =
(refreshRateConfigs == null) ? null
: refreshRateConfigs.getHigherBlockingZoneConfigs();
+ loadPeakDefaultRefreshRate(refreshRateConfigs);
+ loadDefaultRefreshRate(refreshRateConfigs);
loadLowerRefreshRateBlockingZones(lowerBlockingZoneConfig);
loadHigherRefreshRateBlockingZones(higherBlockingZoneConfig);
}
+ private void loadPeakDefaultRefreshRate(RefreshRateConfigs refreshRateConfigs) {
+ if (refreshRateConfigs == null || refreshRateConfigs.getDefaultPeakRefreshRate() == null) {
+ mDefaultPeakRefreshRate = mContext.getResources().getInteger(
+ R.integer.config_defaultPeakRefreshRate);
+ } else {
+ mDefaultPeakRefreshRate =
+ refreshRateConfigs.getDefaultPeakRefreshRate().intValue();
+ }
+ }
+
+ private void loadDefaultRefreshRate(RefreshRateConfigs refreshRateConfigs) {
+ if (refreshRateConfigs == null || refreshRateConfigs.getDefaultRefreshRate() == null) {
+ mDefaultRefreshRate = mContext.getResources().getInteger(
+ R.integer.config_defaultRefreshRate);
+ } else {
+ mDefaultRefreshRate =
+ refreshRateConfigs.getDefaultRefreshRate().intValue();
+ }
+ }
/**
* Loads the refresh rate configurations pertaining to the upper blocking zones.
@@ -1866,10 +1873,10 @@
private void loadHigherBlockingZoneDefaultRefreshRate(
BlockingZoneConfig upperBlockingZoneConfig) {
if (upperBlockingZoneConfig == null) {
- mDefaultHighRefreshRate = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_defaultPeakRefreshRate);
+ mDefaultHighBlockingZoneRefreshRate = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_fixedRefreshRateInHighZone);
} else {
- mDefaultHighRefreshRate =
+ mDefaultHighBlockingZoneRefreshRate =
upperBlockingZoneConfig.getDefaultRefreshRate().intValue();
}
}
@@ -1881,10 +1888,10 @@
private void loadLowerBlockingZoneDefaultRefreshRate(
BlockingZoneConfig lowerBlockingZoneConfig) {
if (lowerBlockingZoneConfig == null) {
- mDefaultLowRefreshRate = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_defaultRefreshRate);
+ mDefaultLowBlockingZoneRefreshRate = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_defaultRefreshRateInZone);
} else {
- mDefaultLowRefreshRate =
+ mDefaultLowBlockingZoneRefreshRate =
lowerBlockingZoneConfig.getDefaultRefreshRate().intValue();
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 3759a8b..70069c6 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -245,7 +245,7 @@
private Display.Mode mUserPreferredMode;
// HDR conversion mode chosen by user
@GuardedBy("mSyncRoot")
- private HdrConversionMode mHdrConversionMode;
+ private HdrConversionMode mHdrConversionMode = null;
// The synchronization root for the display manager.
// This lock guards most of the display manager's state.
@@ -647,6 +647,7 @@
updateSettingsLocked();
updateUserDisabledHdrTypesFromSettingsLocked();
updateUserPreferredDisplayModeSettingsLocked();
+ updateHdrConversionModeSettingsLocked();
}
mDisplayModeDirector.setDesiredDisplayModeSpecsListener(
@@ -1806,6 +1807,31 @@
device.setUserPreferredDisplayModeLocked(modeBuilder.build());
}
+ @GuardedBy("mSyncRoot")
+ private void storeHdrConversionModeLocked(HdrConversionMode hdrConversionMode) {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.HDR_CONVERSION_MODE, hdrConversionMode.getConversionMode());
+ final int preferredHdrOutputType =
+ hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_FORCE
+ ? hdrConversionMode.getPreferredHdrOutputType()
+ : Display.HdrCapabilities.HDR_TYPE_INVALID;
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.HDR_FORCE_CONVERSION_TYPE, preferredHdrOutputType);
+ }
+
+ @GuardedBy("mSyncRoot")
+ void updateHdrConversionModeSettingsLocked() {
+ final int conversionMode = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.HDR_CONVERSION_MODE, HdrConversionMode.HDR_CONVERSION_SYSTEM);
+ final int preferredHdrOutputType = conversionMode == HdrConversionMode.HDR_CONVERSION_FORCE
+ ? Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.HDR_FORCE_CONVERSION_TYPE,
+ Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION)
+ : Display.HdrCapabilities.HDR_TYPE_INVALID;
+ mHdrConversionMode = new HdrConversionMode(conversionMode, preferredHdrOutputType);
+ setHdrConversionModeInternal(mHdrConversionMode);
+ }
+
// If we've never recorded stable device stats for this device before and they aren't
// explicitly configured, go ahead and record the stable device stats now based on the status
// of the default display at first boot.
@@ -1962,13 +1988,14 @@
int[] autoHdrOutputTypes = null;
synchronized (mSyncRoot) {
mHdrConversionMode = hdrConversionMode;
+ storeHdrConversionModeLocked(mHdrConversionMode);
// For auto mode, all supported HDR types are allowed except the ones specifically
// disabled by the user.
if (hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM) {
autoHdrOutputTypes = getEnabledAutoHdrTypesLocked();
}
- DisplayControl.setHdrConversionMode(hdrConversionMode.getConversionMode(),
+ mInjector.setHdrConversionMode(hdrConversionMode.getConversionMode(),
hdrConversionMode.getPreferredHdrOutputType(), autoHdrOutputTypes);
}
}
@@ -1984,7 +2011,7 @@
private @Display.HdrCapabilities.HdrType int[] getSupportedHdrOutputTypesInternal() {
if (mSupportedHdrOutputType == null) {
- mSupportedHdrOutputType = DisplayControl.getSupportedHdrOutputTypes();
+ mSupportedHdrOutputType = mInjector.getSupportedHdrOutputTypes();
}
return mSupportedHdrOutputType;
}
@@ -2604,6 +2631,10 @@
}
}
+ if (mHdrConversionMode != null) {
+ pw.println(" mHdrConversionMode=" + mHdrConversionMode);
+ }
+
pw.println();
final int displayStateCount = mDisplayStates.size();
pw.println("Display States: size=" + displayStateCount);
@@ -2712,6 +2743,16 @@
long getDefaultDisplayDelayTimeout() {
return WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT;
}
+
+ void setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
+ int[] autoHdrTypes) {
+ DisplayControl.setHdrConversionMode(conversionMode, preferredHdrOutputType,
+ autoHdrTypes);
+ }
+
+ int[] getSupportedHdrOutputTypes() {
+ return DisplayControl.getSupportedHdrOutputTypes();
+ }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 72c9a4a..5f6660b 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -1373,7 +1373,7 @@
mDefaultRefreshRate =
(displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
R.integer.config_defaultRefreshRate)
- : (float) displayDeviceConfig.getDefaultLowRefreshRate();
+ : (float) displayDeviceConfig.getDefaultRefreshRate();
}
public void observe() {
@@ -1460,7 +1460,7 @@
defaultPeakRefreshRate =
(displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
R.integer.config_defaultPeakRefreshRate)
- : (float) displayDeviceConfig.getDefaultHighRefreshRate();
+ : (float) displayDeviceConfig.getDefaultPeakRefreshRate();
}
mDefaultPeakRefreshRate = defaultPeakRefreshRate;
}
@@ -1819,8 +1819,26 @@
return mHighAmbientBrightnessThresholds;
}
+ /**
+ * @return the refresh rate to lock to when in a high brightness zone
+ */
+ @VisibleForTesting
+ int getRefreshRateInHighZone() {
+ return mRefreshRateInHighZone;
+ }
+
+ /**
+ * @return the refresh rate to lock to when in a low brightness zone
+ */
+ @VisibleForTesting
+ int getRefreshRateInLowZone() {
+ return mRefreshRateInLowZone;
+ }
+
private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
boolean attemptLoadingFromDeviceConfig) {
+ loadRefreshRateInHighZone(displayDeviceConfig, attemptLoadingFromDeviceConfig);
+ loadRefreshRateInLowZone(displayDeviceConfig, attemptLoadingFromDeviceConfig);
mLowDisplayBrightnessThresholds = loadBrightnessThresholds(
() -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(),
() -> displayDeviceConfig.getLowDisplayBrightnessThresholds(),
@@ -1841,6 +1859,44 @@
}
}
+ private void loadRefreshRateInLowZone(DisplayDeviceConfig displayDeviceConfig,
+ boolean attemptLoadingFromDeviceConfig) {
+ int refreshRateInLowZone =
+ (displayDeviceConfig == null) ? mContext.getResources().getInteger(
+ R.integer.config_defaultRefreshRateInZone)
+ : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate();
+ if (attemptLoadingFromDeviceConfig) {
+ try {
+ refreshRateInLowZone = mDeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE,
+ refreshRateInLowZone);
+ } catch (Exception exception) {
+ // Do nothing
+ }
+ }
+ mRefreshRateInLowZone = refreshRateInLowZone;
+ }
+
+ private void loadRefreshRateInHighZone(DisplayDeviceConfig displayDeviceConfig,
+ boolean attemptLoadingFromDeviceConfig) {
+ int refreshRateInHighZone =
+ (displayDeviceConfig == null) ? mContext.getResources().getInteger(
+ R.integer.config_fixedRefreshRateInHighZone) : displayDeviceConfig
+ .getDefaultHighBlockingZoneRefreshRate();
+ if (attemptLoadingFromDeviceConfig) {
+ try {
+ refreshRateInHighZone = mDeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE,
+ refreshRateInHighZone);
+ } catch (Exception exception) {
+ // Do nothing
+ }
+ }
+ mRefreshRateInHighZone = refreshRateInHighZone;
+ }
+
private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
boolean attemptLoadingFromDeviceConfig) {
mHighDisplayBrightnessThresholds = loadBrightnessThresholds(
@@ -1894,14 +1950,6 @@
}
/**
- * @return the refresh to lock to when in a low brightness zone
- */
- @VisibleForTesting
- int getRefreshRateInLowZone() {
- return mRefreshRateInLowZone;
- }
-
- /**
* @return the display brightness thresholds for the low brightness zones
*/
@VisibleForTesting
@@ -1946,8 +1994,17 @@
mHighAmbientBrightnessThresholds = highAmbientBrightnessThresholds;
}
- mRefreshRateInLowZone = mDeviceConfigDisplaySettings.getRefreshRateInLowZone();
- mRefreshRateInHighZone = mDeviceConfigDisplaySettings.getRefreshRateInHighZone();
+ final int refreshRateInLowZone = mDeviceConfigDisplaySettings
+ .getRefreshRateInLowZone();
+ if (refreshRateInLowZone != -1) {
+ mRefreshRateInLowZone = refreshRateInLowZone;
+ }
+
+ final int refreshRateInHighZone = mDeviceConfigDisplaySettings
+ .getRefreshRateInHighZone();
+ if (refreshRateInHighZone != -1) {
+ mRefreshRateInHighZone = refreshRateInHighZone;
+ }
restartObserver();
mDeviceConfigDisplaySettings.startListening();
@@ -2001,6 +2058,10 @@
restartObserver();
}
+ /**
+ * Used to reload the lower blocking zone refresh rate in case of changes in the
+ * DeviceConfig properties.
+ */
public void onDeviceConfigRefreshRateInLowZoneChanged(int refreshRate) {
if (refreshRate != mRefreshRateInLowZone) {
mRefreshRateInLowZone = refreshRate;
@@ -2024,6 +2085,10 @@
restartObserver();
}
+ /**
+ * Used to reload the higher blocking zone refresh rate in case of changes in the
+ * DeviceConfig properties.
+ */
public void onDeviceConfigRefreshRateInHighZoneChanged(int refreshRate) {
if (refreshRate != mRefreshRateInHighZone) {
mRefreshRateInHighZone = refreshRate;
@@ -2874,15 +2939,10 @@
}
public int getRefreshRateInLowZone() {
- int defaultRefreshRateInZone = mContext.getResources().getInteger(
- R.integer.config_defaultRefreshRateInZone);
-
- int refreshRate = mDeviceConfig.getInt(
+ return mDeviceConfig.getInt(
DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
- DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE,
- defaultRefreshRateInZone);
+ DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, -1);
- return refreshRate;
}
/*
@@ -2904,15 +2964,10 @@
}
public int getRefreshRateInHighZone() {
- int defaultRefreshRateInZone = mContext.getResources().getInteger(
- R.integer.config_fixedRefreshRateInHighZone);
-
- int refreshRate = mDeviceConfig.getInt(
+ return mDeviceConfig.getInt(
DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE,
- defaultRefreshRateInZone);
-
- return refreshRate;
+ -1);
}
public int getRefreshRateInHbmSunlight() {
@@ -2960,23 +3015,29 @@
int[] lowDisplayBrightnessThresholds = getLowDisplayBrightnessThresholds();
int[] lowAmbientBrightnessThresholds = getLowAmbientBrightnessThresholds();
- int refreshRateInLowZone = getRefreshRateInLowZone();
+ final int refreshRateInLowZone = getRefreshRateInLowZone();
mHandler.obtainMessage(MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED,
new Pair<>(lowDisplayBrightnessThresholds, lowAmbientBrightnessThresholds))
.sendToTarget();
- mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone, 0)
- .sendToTarget();
+
+ if (refreshRateInLowZone != -1) {
+ mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone,
+ 0).sendToTarget();
+ }
int[] highDisplayBrightnessThresholds = getHighDisplayBrightnessThresholds();
int[] highAmbientBrightnessThresholds = getHighAmbientBrightnessThresholds();
- int refreshRateInHighZone = getRefreshRateInHighZone();
+ final int refreshRateInHighZone = getRefreshRateInHighZone();
mHandler.obtainMessage(MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED,
new Pair<>(highDisplayBrightnessThresholds, highAmbientBrightnessThresholds))
.sendToTarget();
- mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone, 0)
- .sendToTarget();
+
+ if (refreshRateInHighZone != -1) {
+ mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone,
+ 0).sendToTarget();
+ }
final int refreshRateInHbmSunlight = getRefreshRateInHbmSunlight();
mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index abb0ff6..40eec33 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -504,6 +504,8 @@
private boolean mIsEnabled;
private boolean mIsInTransition;
+ private String mBrightnessThrottlingDataId;
+
// DPCs following the brightness of this DPC. This is used in concurrent displays mode - there
// is one lead display, the additional displays follow the brightness value of the lead display.
@GuardedBy("mLock")
@@ -539,6 +541,7 @@
mHandler = new DisplayControllerHandler(handler.getLooper());
mLastBrightnessEvent = new BrightnessEvent(mDisplayId);
mTempBrightnessEvent = new BrightnessEvent(mDisplayId);
+ mBrightnessThrottlingDataId = logicalDisplay.getBrightnessThrottlingDataIdLocked();
if (mDisplayId == Display.DEFAULT_DISPLAY) {
mBatteryStats = BatteryStatsService.getService();
@@ -862,6 +865,8 @@
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
final boolean isEnabled = mLogicalDisplay.isEnabledLocked();
final boolean isInTransition = mLogicalDisplay.isInTransitionLocked();
+ final String brightnessThrottlingDataId =
+ mLogicalDisplay.getBrightnessThrottlingDataIdLocked();
mHandler.post(() -> {
boolean changed = false;
if (mDisplayDevice != device) {
@@ -870,12 +875,19 @@
mUniqueDisplayId = uniqueId;
mDisplayStatsId = mUniqueDisplayId.hashCode();
mDisplayDeviceConfig = config;
+ mBrightnessThrottlingDataId = brightnessThrottlingDataId;
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
// that we trigger a change immediately.
mPowerState.resetScreenState();
+ } else if (!mBrightnessThrottlingDataId.equals(brightnessThrottlingDataId)) {
+ changed = true;
+ mBrightnessThrottlingDataId = brightnessThrottlingDataId;
+ mBrightnessThrottler.resetThrottlingData(
+ config.getBrightnessThrottlingData(mBrightnessThrottlingDataId),
+ mUniqueDisplayId);
}
if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) {
changed = true;
@@ -887,9 +899,6 @@
updatePowerState();
}
});
-
- // TODO (b/265793751): Re-create BrightnessTracker if we're enetering/exiting concurrent
- // displays mode
}
/**
@@ -926,7 +935,7 @@
}
private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info,
- HighBrightnessModeMetadata hbmMetadata) {
+ HighBrightnessModeMetadata hbmMetadata) {
// All properties that depend on the associated DisplayDevice and the DDC must be
// updated here.
loadBrightnessRampRates();
@@ -948,10 +957,9 @@
return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness);
}
});
- // TODO (b/265793751): Use the appropriate throttling data if we're in concurrent displays
- // mode
mBrightnessThrottler.resetThrottlingData(
- mDisplayDeviceConfig.getBrightnessThrottlingData(), mUniqueDisplayId);
+ mDisplayDeviceConfig.getBrightnessThrottlingData(mBrightnessThrottlingDataId),
+ mUniqueDisplayId);
}
private void sendUpdatePowerState() {
@@ -2061,11 +2069,8 @@
private BrightnessThrottler createBrightnessThrottlerLocked() {
final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
- // TODO (b/265793751): Use the appropriate throttling data if we're in concurrent displays
- // mode
- final DisplayDeviceConfig.BrightnessThrottlingData data =
- ddConfig != null ? ddConfig.getBrightnessThrottlingData() : null;
- return new BrightnessThrottler(mHandler, data,
+ return new BrightnessThrottler(mHandler,
+ ddConfig.getBrightnessThrottlingData(mBrightnessThrottlingDataId),
() -> {
sendUpdatePowerState();
postBrightnessChangeRunnable();
@@ -2200,15 +2205,13 @@
}
private void loadProximitySensor() {
- if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) {
+ if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT || mDisplayId != Display.DEFAULT_DISPLAY) {
return;
}
final DisplayDeviceConfig.SensorData proxSensor =
mDisplayDeviceConfig.getProximitySensor();
- final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY
- ? Sensor.TYPE_PROXIMITY : SensorUtils.NO_FALLBACK;
mProximitySensor = SensorUtils.findSensor(mSensorManager, proxSensor.type, proxSensor.name,
- fallbackType);
+ Sensor.TYPE_PROXIMITY);
if (mProximitySensor != null) {
mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
TYPICAL_PROXIMITY_THRESHOLD);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 2e91bdb..6092ad7 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -411,6 +411,8 @@
private boolean mIsEnabled;
private boolean mIsInTransition;
+ private String mBrightnessThrottlingDataId;
+
// DPCs following the brightness of this DPC. This is used in concurrent displays mode - there
// is one lead display, the additional displays follow the brightness value of the lead display.
@GuardedBy("mLock")
@@ -443,6 +445,7 @@
mHighBrightnessModeMetadata = hbmMetadata;
mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
mTag = "DisplayPowerController2[" + mDisplayId + "]";
+ mBrightnessThrottlingDataId = logicalDisplay.getBrightnessThrottlingDataIdLocked();
mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
@@ -705,6 +708,8 @@
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
final boolean isEnabled = mLogicalDisplay.isEnabledLocked();
final boolean isInTransition = mLogicalDisplay.isInTransitionLocked();
+ final String brightnessThrottlingDataId =
+ mLogicalDisplay.getBrightnessThrottlingDataIdLocked();
mHandler.post(() -> {
boolean changed = false;
@@ -714,6 +719,7 @@
mUniqueDisplayId = uniqueId;
mDisplayStatsId = mUniqueDisplayId.hashCode();
mDisplayDeviceConfig = config;
+ mBrightnessThrottlingDataId = brightnessThrottlingDataId;
loadFromDisplayDeviceConfig(token, info, hbmMetadata);
mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(config);
@@ -721,6 +727,12 @@
// last command that was sent to change it's state. Lets assume it is unknown so
// that we trigger a change immediately.
mPowerState.resetScreenState();
+ } else if (!mBrightnessThrottlingDataId.equals(brightnessThrottlingDataId)) {
+ changed = true;
+ mBrightnessThrottlingDataId = brightnessThrottlingDataId;
+ mBrightnessThrottler.resetThrottlingData(
+ config.getBrightnessThrottlingData(mBrightnessThrottlingDataId),
+ mUniqueDisplayId);
}
if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) {
changed = true;
@@ -732,9 +744,6 @@
updatePowerState();
}
});
-
- // TODO (b/265793751): Re-create BrightnessTracker if we're enetering/exiting concurrent
- // displays mode
}
/**
@@ -765,7 +774,7 @@
}
private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info,
- HighBrightnessModeMetadata hbmMetadata) {
+ HighBrightnessModeMetadata hbmMetadata) {
// All properties that depend on the associated DisplayDevice and the DDC must be
// updated here.
loadBrightnessRampRates();
@@ -786,10 +795,9 @@
return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness);
}
});
- // TODO (b/265793751): Use the appropriate throttling data if we're in concurrent displays
- // mode
mBrightnessThrottler.resetThrottlingData(
- mDisplayDeviceConfig.getBrightnessThrottlingData(), mUniqueDisplayId);
+ mDisplayDeviceConfig.getBrightnessThrottlingData(mBrightnessThrottlingDataId),
+ mUniqueDisplayId);
}
private void sendUpdatePowerState() {
@@ -1768,11 +1776,8 @@
private BrightnessThrottler createBrightnessThrottlerLocked() {
final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
- // TODO (b/265793751): Use the appropriate throttling data if we're in concurrent displays
- // mode
- final DisplayDeviceConfig.BrightnessThrottlingData data =
- ddConfig != null ? ddConfig.getBrightnessThrottlingData() : null;
- return new BrightnessThrottler(mHandler, data,
+ return new BrightnessThrottler(mHandler,
+ ddConfig.getBrightnessThrottlingData(mBrightnessThrottlingDataId),
() -> {
sendUpdatePowerState();
postBrightnessChangeRunnable();
diff --git a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
index a3433d9..5bb3e6b 100644
--- a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
@@ -346,15 +346,13 @@
}
private void loadProximitySensor() {
- if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) {
+ if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT || mDisplayId != Display.DEFAULT_DISPLAY) {
return;
}
final DisplayDeviceConfig.SensorData proxSensor =
mDisplayDeviceConfig.getProximitySensor();
- final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY
- ? Sensor.TYPE_PROXIMITY : SensorUtils.NO_FALLBACK;
mProximitySensor = SensorUtils.findSensor(mSensorManager, proxSensor.type, proxSensor.name,
- fallbackType);
+ Sensor.TYPE_PROXIMITY);
if (mProximitySensor != null) {
mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
TYPICAL_PROXIMITY_THRESHOLD);
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index dfdbce5..4bb1f0e 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -31,6 +31,7 @@
import android.view.Surface;
import android.view.SurfaceControl;
+import com.android.server.display.layout.Layout;
import com.android.server.wm.utils.InsetUtils;
import java.io.PrintWriter;
@@ -152,6 +153,17 @@
// the {@link mIsEnabled} is changing, or the underlying mPrimiaryDisplayDevice is changing.
private boolean mIsInTransition;
+ // Indicates the position of the display, POSITION_UNKNOWN could mean it hasn't been specified,
+ // or this is a virtual display etc.
+ private int mPosition = Layout.Display.POSITION_UNKNOWN;
+
+ /**
+ * The ID of the brightness throttling data that should be used. This can change e.g. in
+ * concurrent displays mode in which a stricter brightness throttling policy might need to be
+ * used.
+ */
+ private String mBrightnessThrottlingDataId;
+
public LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
mDisplayId = displayId;
mLayerStack = layerStack;
@@ -160,6 +172,14 @@
mTempFrameRateOverride = new SparseArray<>();
mIsEnabled = true;
mIsInTransition = false;
+ mBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID;
+ }
+
+ public void setPositionLocked(int position) {
+ mPosition = position;
+ }
+ public int getPositionLocked() {
+ return mPosition;
}
/**
@@ -416,6 +436,11 @@
mBaseDisplayInfo.roundedCorners = deviceInfo.roundedCorners;
mBaseDisplayInfo.installOrientation = deviceInfo.installOrientation;
mBaseDisplayInfo.displayShape = deviceInfo.displayShape;
+
+ if (mPosition == Layout.Display.POSITION_REAR) {
+ mBaseDisplayInfo.flags |= Display.FLAG_REAR;
+ }
+
mPrimaryDisplayDeviceInfo = deviceInfo;
mInfo.set(null);
}
@@ -762,7 +787,7 @@
/**
* Sets the display as enabled.
*
- * @param enable True if enabled, false otherwise.
+ * @param enabled True if enabled, false otherwise.
*/
public void setEnabledLocked(boolean enabled) {
mIsEnabled = enabled;
@@ -785,11 +810,28 @@
mIsInTransition = isInTransition;
}
+ /**
+ * @return The ID of the brightness throttling data that this display should use.
+ */
+ public String getBrightnessThrottlingDataIdLocked() {
+ return mBrightnessThrottlingDataId;
+ }
+
+ /**
+ * @param brightnessThrottlingDataId The ID of the brightness throttling data that this
+ * display should use.
+ */
+ public void setBrightnessThrottlingDataIdLocked(String brightnessThrottlingDataId) {
+ mBrightnessThrottlingDataId =
+ brightnessThrottlingDataId;
+ }
+
public void dumpLocked(PrintWriter pw) {
pw.println("mDisplayId=" + mDisplayId);
pw.println("mIsEnabled=" + mIsEnabled);
pw.println("mIsInTransition=" + mIsInTransition);
pw.println("mLayerStack=" + mLayerStack);
+ pw.println("mPosition=" + mPosition);
pw.println("mHasContent=" + mHasContent);
pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}");
pw.println("mRequestedColorMode=" + mRequestedColorMode);
@@ -802,6 +844,7 @@
pw.println("mRequestedMinimalPostProcessing=" + mRequestedMinimalPostProcessing);
pw.println("mFrameRateOverrides=" + Arrays.toString(mFrameRateOverrides));
pw.println("mPendingFrameRateOverrideUids=" + mPendingFrameRateOverrideUids);
+ pw.println("mBrightnessThrottlingDataId=" + mBrightnessThrottlingDataId);
}
@Override
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 5087caa..a67644b 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -638,7 +638,8 @@
& DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY) != 0
&& !nextDeviceInfo.address.equals(deviceInfo.address)) {
layout.createDisplayLocked(nextDeviceInfo.address,
- /* isDefault= */ true, /* isEnabled= */ true, mIdProducer);
+ /* isDefault= */ true, /* isEnabled= */ true, mIdProducer,
+ /* brightnessThrottlingMapId= */ null);
applyLayoutLocked();
return;
}
@@ -976,6 +977,7 @@
// Now that we have a display-device, we need a LogicalDisplay to map it to. Find the
// right one, if it doesn't exist, create a new one.
final int logicalDisplayId = displayLayout.getLogicalDisplayId();
+
LogicalDisplay newDisplay = getDisplayLocked(logicalDisplayId);
if (newDisplay == null) {
newDisplay = createNewLogicalDisplayLocked(
@@ -988,7 +990,12 @@
newDisplay.swapDisplaysLocked(oldDisplay);
}
+ newDisplay.setPositionLocked(displayLayout.getPosition());
setEnabledLocked(newDisplay, displayLayout.isEnabled());
+ newDisplay.setBrightnessThrottlingDataIdLocked(
+ displayLayout.getBrightnessThrottlingMapId() == null
+ ? DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID
+ : displayLayout.getBrightnessThrottlingMapId());
}
}
@@ -1063,7 +1070,7 @@
}
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
layout.createDisplayLocked(info.address, /* isDefault= */ true, /* isEnabled= */ true,
- mIdProducer);
+ mIdProducer, /* brightnessThrottlingMapId= */ null);
}
private int assignLayerStackLocked(int displayId) {
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index ddeaa1b..364d53b 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -106,7 +106,8 @@
String name = virtualDisplayConfig.getName();
boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0;
IBinder appToken = callback.asBinder();
- IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure);
+ IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure,
+ virtualDisplayConfig.getRequestedRefreshRate());
final String baseUniqueId =
UNIQUE_ID_PREFIX + ownerPackageName + "," + ownerUid + "," + name + ",";
final int uniqueIndex = getNextUniqueIndex(baseUniqueId);
@@ -247,6 +248,7 @@
private int mWidth;
private int mHeight;
private int mDensityDpi;
+ private float mRequestedRefreshRate;
private Surface mSurface;
private DisplayDeviceInfo mInfo;
private int mDisplayState;
@@ -270,8 +272,9 @@
mName = virtualDisplayConfig.getName();
mWidth = virtualDisplayConfig.getWidth();
mHeight = virtualDisplayConfig.getHeight();
- mMode = createMode(mWidth, mHeight, REFRESH_RATE);
mDensityDpi = virtualDisplayConfig.getDensityDpi();
+ mRequestedRefreshRate = virtualDisplayConfig.getRequestedRefreshRate();
+ mMode = createMode(mWidth, mHeight, getRefreshRate());
mSurface = surface;
mFlags = flags;
mCallback = callback;
@@ -410,7 +413,7 @@
sendTraversalRequestLocked();
mWidth = width;
mHeight = height;
- mMode = createMode(width, height, REFRESH_RATE);
+ mMode = createMode(width, height, getRefreshRate());
mDensityDpi = densityDpi;
mInfo = null;
mPendingChanges |= PENDING_RESIZE;
@@ -438,9 +441,9 @@
pw.println("mStopped=" + mStopped);
pw.println("mDisplayIdToMirror=" + mDisplayIdToMirror);
pw.println("mWindowManagerMirroring=" + mIsWindowManagerMirroring);
+ pw.println("mRequestedRefreshRate=" + mRequestedRefreshRate);
}
-
@Override
public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
if (mInfo == null) {
@@ -456,7 +459,7 @@
mInfo.densityDpi = mDensityDpi;
mInfo.xDpi = mDensityDpi;
mInfo.yDpi = mDensityDpi;
- mInfo.presentationDeadlineNanos = 1000000000L / (int) REFRESH_RATE; // 1 frame
+ mInfo.presentationDeadlineNanos = 1000000000L / (int) getRefreshRate(); // 1 frame
mInfo.flags = 0;
if ((mFlags & VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE
@@ -554,6 +557,10 @@
}
return mInfo;
}
+
+ private float getRefreshRate() {
+ return (mRequestedRefreshRate != 0.0f) ? mRequestedRefreshRate : REFRESH_RATE;
+ }
}
private static class Callback extends Handler {
@@ -632,6 +639,19 @@
@VisibleForTesting
public interface SurfaceControlDisplayFactory {
- public IBinder createDisplay(String name, boolean secure);
+ /**
+ * Create a virtual display in SurfaceFlinger.
+ *
+ * @param name The name of the display
+ * @param secure Whether this display is secure.
+ * @param requestedRefreshRate
+ * The refresh rate, frames per second, to request on the virtual display.
+ * It should be a divisor of refresh rate of the leader physical display
+ * that drives VSYNC, e.g. 30/60fps on 120fps display. If an arbitrary refresh
+ * rate is specified, SurfaceFlinger rounds up or down to match a divisor of
+ * the refresh rate of the leader physical display.
+ * @return The token reference for the display in SurfaceFlinger.
+ */
+ IBinder createDisplay(String name, boolean secure, float requestedRefreshRate);
}
}
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index 4a466fd..01aa97a 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -18,6 +18,8 @@
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.server.display.layout.Layout.Display.POSITION_UNKNOWN;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.Slog;
@@ -25,6 +27,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* Holds a collection of {@link Display}s. A single instance of this class describes
@@ -76,7 +79,23 @@
*/
public Display createDisplayLocked(
@NonNull DisplayAddress address, boolean isDefault, boolean isEnabled,
- DisplayIdProducer idProducer) {
+ DisplayIdProducer idProducer, String brightnessThrottlingMapId) {
+ return createDisplayLocked(address, isDefault, isEnabled, idProducer,
+ brightnessThrottlingMapId, POSITION_UNKNOWN);
+ }
+
+ /**
+ * Creates a simple 1:1 LogicalDisplay mapping for the specified DisplayDevice.
+ *
+ * @param address Address of the device.
+ * @param isDefault Indicates if the device is meant to be the default display.
+ * @param isEnabled Indicates if this display is usable and can be switched on
+ * @param position Indicates the position this display is facing in this layout.
+ * @return The new layout.
+ */
+ public Display createDisplayLocked(
+ @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled,
+ DisplayIdProducer idProducer, String brightnessThrottlingMapId, int position) {
if (contains(address)) {
Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
return null;
@@ -93,7 +112,8 @@
// different layouts, a logical display can be destroyed and later recreated with the
// same logical display ID.
final int logicalDisplayId = idProducer.getId(isDefault);
- final Display display = new Display(address, logicalDisplayId, isEnabled);
+ final Display display = new Display(address, logicalDisplayId, isEnabled,
+ brightnessThrottlingMapId, position);
mDisplays.add(display);
return display;
@@ -195,11 +215,19 @@
// {@link DeviceStateToLayoutMap.POSITION_UNKNOWN} is unspecified.
private int mPosition;
- Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled) {
+ // The ID of the brightness throttling map that should be used. This can change e.g. in
+ // concurrent displays mode in which a stricter brightness throttling policy might need to
+ // be used.
+ @Nullable
+ private final String mBrightnessThrottlingMapId;
+
+ Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled,
+ String brightnessThrottlingMapId, int position) {
mAddress = address;
mLogicalDisplayId = logicalDisplayId;
mIsEnabled = isEnabled;
- mPosition = POSITION_UNKNOWN;
+ mPosition = position;
+ mBrightnessThrottlingMapId = brightnessThrottlingMapId;
}
@Override
@@ -209,6 +237,7 @@
+ "(" + (mIsEnabled ? "ON" : "OFF") + ")"
+ ", addr: " + mAddress
+ ((mPosition == POSITION_UNKNOWN) ? "" : ", position: " + mPosition)
+ + ", brightnessThrottlingMapId: " + mBrightnessThrottlingMapId
+ "}";
}
@@ -223,7 +252,9 @@
return otherDisplay.mIsEnabled == this.mIsEnabled
&& otherDisplay.mPosition == this.mPosition
&& otherDisplay.mLogicalDisplayId == this.mLogicalDisplayId
- && this.mAddress.equals(otherDisplay.mAddress);
+ && this.mAddress.equals(otherDisplay.mAddress)
+ && Objects.equals(mBrightnessThrottlingMapId,
+ otherDisplay.mBrightnessThrottlingMapId);
}
@Override
@@ -233,6 +264,7 @@
result = 31 * result + mPosition;
result = 31 * result + mLogicalDisplayId;
result = 31 * result + mAddress.hashCode();
+ result = 31 * result + mBrightnessThrottlingMapId.hashCode();
return result;
}
@@ -251,5 +283,16 @@
public void setPosition(int position) {
mPosition = position;
}
+
+ /**
+ * @return The ID of the brightness throttling map that this display should use.
+ */
+ public String getBrightnessThrottlingMapId() {
+ return mBrightnessThrottlingMapId;
+ }
+
+ public int getPosition() {
+ return mPosition;
+ }
}
}
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
index 85d5b4f..5b772fc 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
@@ -419,16 +419,16 @@
float ambientBrightness = mBrightnessFilter.getEstimate(time);
mLatestAmbientBrightness = ambientBrightness;
- if (ambientColorTemperature != -1.0f &&
- mLowLightAmbientBrightnessToBiasSpline != null) {
+ if (ambientColorTemperature != -1.0f && ambientBrightness != -1.0f
+ && mLowLightAmbientBrightnessToBiasSpline != null) {
float bias = mLowLightAmbientBrightnessToBiasSpline.interpolate(ambientBrightness);
ambientColorTemperature =
bias * ambientColorTemperature + (1.0f - bias)
* mLowLightAmbientColorTemperature;
mLatestLowLightBias = bias;
}
- if (ambientColorTemperature != -1.0f &&
- mHighLightAmbientBrightnessToBiasSpline != null) {
+ if (ambientColorTemperature != -1.0f && ambientBrightness != -1.0f
+ && mHighLightAmbientBrightnessToBiasSpline != null) {
float bias = mHighLightAmbientBrightnessToBiasSpline.interpolate(ambientBrightness);
ambientColorTemperature =
(1.0f - bias) * ambientColorTemperature + bias
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index d9cdba7..caa5036 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -72,6 +72,7 @@
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.input.InputManagerInternal;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.ActivityInterceptorCallback;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -639,8 +640,10 @@
}
private boolean dreamsEnabledForUser(int userId) {
- // TODO(b/257333623): Support non-system Dock Users in HSUM.
- return !mDreamsOnlyEnabledForDockUser || (userId == UserHandle.USER_SYSTEM);
+ if (!mDreamsOnlyEnabledForDockUser) return true;
+ if (userId < 0) return false;
+ final int mainUserId = LocalServices.getService(UserManagerInternal.class).getMainUserId();
+ return userId == mainUserId;
}
private ServiceInfo getServiceInfo(ComponentName name) {
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 1a357ee..41053e9 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -21,11 +21,14 @@
import android.annotation.Nullable;
import android.app.IGrammaticalInflectionManager;
import android.content.Context;
+import android.content.pm.PackageManagerInternal;
import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
import android.os.SystemProperties;
+import android.util.Log;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -36,9 +39,10 @@
* <p>This service is API entry point for storing app-specific grammatical inflection.
*/
public class GrammaticalInflectionService extends SystemService {
-
+ private final String TAG = "GrammaticalInflection";
private final GrammaticalInflectionBackupHelper mBackupHelper;
private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+ private PackageManagerInternal mPackageManagerInternal;
private static final String GRAMMATICAL_INFLECTION_ENABLED =
"i18n.grammatical_Inflection.enabled";
@@ -55,6 +59,7 @@
public GrammaticalInflectionService(Context context) {
super(context);
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mBackupHelper = new GrammaticalInflectionBackupHelper(
this, context.getPackageManager());
}
@@ -110,14 +115,26 @@
protected void setRequestedApplicationGrammaticalGender(
String appPackageName, int userId, int gender) {
- if (!SystemProperties.getBoolean(GRAMMATICAL_INFLECTION_ENABLED, true)) {
- return;
- }
-
+ int preValue = getApplicationGrammaticalGender(appPackageName, userId);
final ActivityTaskManagerInternal.PackageConfigurationUpdater updater =
mActivityTaskManagerInternal.createPackageConfigurationUpdater(appPackageName,
userId);
+ if (!SystemProperties.getBoolean(GRAMMATICAL_INFLECTION_ENABLED, true)) {
+ if (preValue != GRAMMATICAL_GENDER_NOT_SPECIFIED) {
+ Log.d(TAG, "Clearing the user's grammatical gender setting");
+ updater.setGrammaticalGender(GRAMMATICAL_GENDER_NOT_SPECIFIED).commit();
+ }
+ return;
+ }
+
+ final int uid = mPackageManagerInternal.getPackageUid(appPackageName, 0, userId);
+ FrameworkStatsLog.write(FrameworkStatsLog.GRAMMATICAL_INFLECTION_CHANGED,
+ FrameworkStatsLog.APPLICATION_GRAMMATICAL_INFLECTION_CHANGED__SOURCE_ID__OTHERS,
+ uid,
+ gender != GRAMMATICAL_GENDER_NOT_SPECIFIED,
+ preValue != GRAMMATICAL_GENDER_NOT_SPECIFIED);
+
updater.setGrammaticalGender(gender).commit();
}
}
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index e21895a..298f6ad 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -27,7 +27,6 @@
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME_IMPLICIT;
import android.annotation.Nullable;
-import android.os.Binder;
import android.os.IBinder;
import android.os.ResultReceiver;
import android.util.EventLog;
@@ -65,13 +64,10 @@
@GuardedBy("ImfLock.class")
@Override
- public void performShowIme(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
+ public void performShowIme(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
int showFlags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
final IInputMethodInvoker curMethod = mService.getCurMethodLocked();
if (curMethod != null) {
- // create a placeholder token for IMS so that IMS cannot inject windows into client app.
- final IBinder showInputToken = new Binder();
- mService.setRequestImeTokenToWindow(windowToken, showInputToken);
if (DEBUG) {
Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken
+ ", " + showFlags + ", " + resultReceiver + ") for reason: "
@@ -86,7 +82,7 @@
InputMethodDebug.softInputModeToString(
mService.mCurFocusedWindowSoftInputMode));
}
- mService.onShowHideSoftInputRequested(true /* show */, windowToken, reason,
+ mService.onShowHideSoftInputRequested(true /* show */, showInputToken, reason,
statsToken);
}
}
@@ -94,12 +90,10 @@
@GuardedBy("ImfLock.class")
@Override
- public void performHideIme(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
+ public void performHideIme(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken,
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
final IInputMethodInvoker curMethod = mService.getCurMethodLocked();
if (curMethod != null) {
- final Binder hideInputToken = new Binder();
- mService.setRequestImeTokenToWindow(windowToken, hideInputToken);
// The IME will report its visible state again after the following message finally
// delivered to the IME process as an IPC. Hence the inconsistency between
// IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
@@ -118,7 +112,7 @@
InputMethodDebug.softInputModeToString(
mService.mCurFocusedWindowSoftInputMode));
}
- mService.onShowHideSoftInputRequested(false /* show */, windowToken, reason,
+ mService.onShowHideSoftInputRequested(false /* show */, hideInputToken, reason,
statsToken);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java
index e97ec93..f03e985 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java
@@ -31,26 +31,26 @@
/**
* Performs showing IME on top of the given window.
*
- * @param windowToken The token of a window that currently has focus.
+ * @param showInputToken A token that represents the requester to show IME.
* @param statsToken A token that tracks the progress of an IME request.
* @param showFlags Provides additional operating flags to show IME.
* @param resultReceiver If non-null, this will be called back to the caller when
* it has processed request to tell what it has done.
* @param reason The reason for requesting to show IME.
*/
- default void performShowIme(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
+ default void performShowIme(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
int showFlags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {}
/**
* Performs hiding IME to the given window
*
- * @param windowToken The token of a window that currently has focus.
+ * @param hideInputToken A token that represents the requester to hide IME.
* @param statsToken A token that tracks the progress of an IME request.
* @param resultReceiver If non-null, this will be called back to the caller when
* it has processed request to tell what it has done.
* @param reason The reason for requesting to hide IME.
*/
- default void performHideIme(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
+ default void performHideIme(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken,
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {}
/**
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index db61e954..eaca842 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -35,6 +35,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.content.res.Configuration;
+import android.os.Binder;
import android.os.IBinder;
import android.util.PrintWriterPrinter;
import android.util.Printer;
@@ -263,6 +264,8 @@
// policy.
mPolicy.mPendingA11yRequestingHideKeyboard = false;
}
+ // create a placeholder token for IMS so that IMS cannot inject windows into client app.
+ state.setRequestImeToken(new Binder());
setWindowStateInner(windowToken, state);
}
@@ -279,14 +282,6 @@
return state;
}
- void setRequestImeTokenToWindow(IBinder windowToken, IBinder token) {
- ImeTargetWindowState state = getWindowStateOrNull(windowToken);
- if (state != null) {
- state.setRequestImeToken(token);
- setWindowStateInner(windowToken, state);
- }
- }
-
void setWindowState(IBinder windowToken, @NonNull ImeTargetWindowState newState) {
final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
if (state != null && newState.hasEdiorFocused()) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 2ced988..27daceb 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3344,11 +3344,6 @@
}
}
- @GuardedBy("ImfLock.class")
- void setRequestImeTokenToWindow(IBinder windowToken, IBinder token) {
- mVisibilityStateComputer.setRequestImeTokenToWindow(windowToken, token);
- }
-
@BinderThread
@Override
public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
@@ -4603,10 +4598,13 @@
}
}
- /** Called right after {@link com.android.internal.inputmethod.IInputMethod#showSoftInput}. */
+ /**
+ * Called right after {@link IInputMethod#showSoftInput} or {@link IInputMethod#hideSoftInput}.
+ */
@GuardedBy("ImfLock.class")
- void onShowHideSoftInputRequested(boolean show, IBinder requestToken,
+ void onShowHideSoftInputRequested(boolean show, IBinder requestImeToken,
@SoftInputShowHideReason int reason, @Nullable ImeTracker.Token statsToken) {
+ final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken);
final WindowManagerInternal.ImeTargetInfo info =
mWindowManagerInternal.onToggleImeRequested(
show, mCurFocusedWindow, requestToken, mCurTokenDisplayId);
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index cd972dc..ea000a0 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -513,7 +513,7 @@
WeaverConfig weaverConfig = null;
try {
weaverConfig = weaver.getConfig();
- } catch (RemoteException e) {
+ } catch (RemoteException | ServiceSpecificException e) {
Slog.e(TAG, "Failed to get weaver config", e);
}
if (weaverConfig == null || weaverConfig.slots <= 0) {
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index ffc309e..3c97aaf8 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -280,13 +280,13 @@
public void setRouteListingPreference(
@NonNull IMediaRouter2 router,
@Nullable RouteListingPreference routeListingPreference) {
- ComponentName inAppOnlyItemRoutingReceiver =
+ ComponentName linkedItemLandingComponent =
routeListingPreference != null
- ? routeListingPreference.getInAppOnlyItemRoutingReceiver()
+ ? routeListingPreference.getLinkedItemComponentName()
: null;
- if (inAppOnlyItemRoutingReceiver != null) {
+ if (linkedItemLandingComponent != null) {
MediaServerUtils.enforcePackageName(
- inAppOnlyItemRoutingReceiver.getPackageName(), Binder.getCallingUid());
+ linkedItemLandingComponent.getPackageName(), Binder.getCallingUid());
}
final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index f2a39b8..26e8fe6 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -21,11 +21,11 @@
import android.media.Session2Token;
import android.media.session.MediaSession;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Log;
+import android.util.Slog;
import android.util.SparseArray;
-import com.android.server.utils.EventLogger;
-
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -61,8 +61,6 @@
private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
private final OnMediaButtonSessionChangedListener mOnMediaButtonSessionChangedListener;
- private final EventLogger mEventLogger = new EventLogger(DUMP_EVENTS_MAX_COUNT, TAG);
-
/**
* The media button session which receives media key events.
* It could be null if the previous media button session is released.
@@ -86,9 +84,8 @@
* @param record The record to add.
*/
public void addSession(MediaSessionRecordImpl record) {
- mEventLogger.enqueue(EventLogger.StringEvent.from(
- "addSession() (to bottom of stack)",
- "record: %s",
+ Slog.i(TAG, TextUtils.formatSimple(
+ "addSession to bottom of stack | record: %s",
record
));
mSessions.add(record);
@@ -106,9 +103,8 @@
* @param record The record to remove.
*/
public void removeSession(MediaSessionRecordImpl record) {
- mEventLogger.enqueue(EventLogger.StringEvent.from(
- "removeSession()",
- "record: %s",
+ Slog.i(TAG, TextUtils.formatSimple(
+ "removeSession | record: %s",
record
));
mSessions.remove(record);
@@ -156,9 +152,8 @@
public void onPlaybackStateChanged(
MediaSessionRecordImpl record, boolean shouldUpdatePriority) {
if (shouldUpdatePriority) {
- mEventLogger.enqueue(EventLogger.StringEvent.from(
- "onPlaybackStateChanged() - Pushing session to top",
- "record: %s",
+ Slog.i(TAG, TextUtils.formatSimple(
+ "onPlaybackStateChanged - Pushing session to top | record: %s",
record
));
mSessions.remove(record);
@@ -365,8 +360,6 @@
for (MediaSessionRecordImpl record : mSessions) {
record.dump(pw, indent);
}
- pw.println(prefix + "Session stack events:");
- mEventLogger.dump(pw, indent);
}
/**
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 5ab4a42..14ae2a7 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -521,8 +521,6 @@
private PackageManager mPackageManagerClient;
PackageManagerInternal mPackageManagerInternal;
private PermissionPolicyInternal mPermissionPolicyInternal;
-
- private PermissionManagerServiceInternal mPermissionInternal;
AudioManager mAudioManager;
AudioManagerInternal mAudioManagerInternal;
// Can be null for wear
@@ -2212,8 +2210,7 @@
TelephonyManager telephonyManager, ActivityManagerInternal ami,
MultiRateLimiter toastRateLimiter, PermissionHelper permissionHelper,
UsageStatsManagerInternal usageStatsManagerInternal,
- TelecomManager telecomManager, NotificationChannelLogger channelLogger,
- PermissionManagerServiceInternal permInternal) {
+ TelecomManager telecomManager, NotificationChannelLogger channelLogger) {
mHandler = handler;
Resources resources = getContext().getResources();
mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
@@ -2231,7 +2228,6 @@
mPackageManagerClient = packageManagerClient;
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mPermissionPolicyInternal = LocalServices.getService(PermissionPolicyInternal.class);
- mPermissionInternal = permInternal;
mUmInternal = LocalServices.getService(UserManagerInternal.class);
mUsageStatsManagerInternal = usageStatsManagerInternal;
mAppOps = appOps;
@@ -2543,8 +2539,7 @@
AppGlobals.getPermissionManager()),
LocalServices.getService(UsageStatsManagerInternal.class),
getContext().getSystemService(TelecomManager.class),
- new NotificationChannelLoggerImpl(),
- LocalServices.getService(PermissionManagerServiceInternal.class));
+ new NotificationChannelLoggerImpl());
publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL);
@@ -6694,30 +6689,17 @@
(userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId);
Notification.addFieldsFromContext(ai, notification);
- int canColorize = mPermissionInternal.checkPermission(
- android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, pkg, userId);
+ int canColorize = mPackageManagerClient.checkPermission(
+ android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, pkg);
if (canColorize == PERMISSION_GRANTED) {
notification.flags |= Notification.FLAG_CAN_COLORIZE;
} else {
notification.flags &= ~Notification.FLAG_CAN_COLORIZE;
}
- if (notification.extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, false)) {
- int hasShowDuringSetupPerm = mPermissionInternal.checkPermission(
- android.Manifest.permission.NOTIFICATION_DURING_SETUP, pkg, userId);
- if (hasShowDuringSetupPerm != PERMISSION_GRANTED) {
- notification.extras.remove(Notification.EXTRA_ALLOW_DURING_SETUP);
- if (DBG) {
- Slog.w(TAG, "warning: pkg " + pkg + " attempting to show during setup"
- + " without holding perm "
- + Manifest.permission.NOTIFICATION_DURING_SETUP);
- }
- }
- }
-
if (notification.fullScreenIntent != null && ai.targetSdkVersion >= Build.VERSION_CODES.Q) {
- int fullscreenIntentPermission = mPermissionInternal.checkPermission(
- android.Manifest.permission.USE_FULL_SCREEN_INTENT, pkg, userId);
+ int fullscreenIntentPermission = mPackageManagerClient.checkPermission(
+ android.Manifest.permission.USE_FULL_SCREEN_INTENT, pkg);
if (fullscreenIntentPermission != PERMISSION_GRANTED) {
notification.fullScreenIntent = null;
Slog.w(TAG, "Package " + pkg +
@@ -6725,6 +6707,31 @@
}
}
+ // Ensure all actions are present
+ if (notification.actions != null) {
+ boolean hasNullActions = false;
+ int nActions = notification.actions.length;
+ for (int i = 0; i < nActions; i++) {
+ if (notification.actions[i] == null) {
+ hasNullActions = true;
+ break;
+ }
+ }
+ if (hasNullActions) {
+ ArrayList<Notification.Action> nonNullActions = new ArrayList<>();
+ for (int i = 0; i < nActions; i++) {
+ if (notification.actions[i] != null) {
+ nonNullActions.add(notification.actions[i]);
+ }
+ }
+ if (nonNullActions.size() != 0) {
+ notification.actions = nonNullActions.toArray(new Notification.Action[0]);
+ } else {
+ notification.actions = null;
+ }
+ }
+ }
+
// Ensure CallStyle has all the correct actions
if (notification.isStyle(Notification.CallStyle.class)) {
Notification.Builder builder =
@@ -6737,7 +6744,7 @@
// Ensure MediaStyle has correct permissions for remote device extras
if (notification.isStyle(Notification.MediaStyle.class)) {
- int hasMediaContentControlPermission = mPermissionInternal.checkPermission(
+ int hasMediaContentControlPermission = mPackageManager.checkPermission(
android.Manifest.permission.MEDIA_CONTENT_CONTROL, pkg, userId);
if (hasMediaContentControlPermission != PERMISSION_GRANTED) {
notification.extras.remove(Notification.EXTRA_MEDIA_REMOTE_DEVICE);
@@ -6752,7 +6759,7 @@
// Ensure only allowed packages have a substitute app name
if (notification.extras.containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME)) {
- int hasSubstituteAppNamePermission = mPermissionInternal.checkPermission(
+ int hasSubstituteAppNamePermission = mPackageManager.checkPermission(
permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg, userId);
if (hasSubstituteAppNamePermission != PERMISSION_GRANTED) {
notification.extras.remove(Notification.EXTRA_SUBSTITUTE_APP_NAME);
@@ -8356,9 +8363,15 @@
}
private boolean isExemptFromRateLimiting(String pkg, int userId) {
- return mPermissionInternal.checkPermission(
- android.Manifest.permission.UNLIMITED_TOASTS, pkg, userId)
- == PackageManager.PERMISSION_GRANTED;
+ boolean isExemptFromRateLimiting = false;
+ try {
+ isExemptFromRateLimiting = mPackageManager.checkPermission(
+ android.Manifest.permission.UNLIMITED_TOASTS, pkg, userId)
+ == PackageManager.PERMISSION_GRANTED;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to connect with package manager");
+ }
+ return isExemptFromRateLimiting;
}
/** Reports rate limiting toasts compat change (used when the toast was blocked). */
@@ -10005,9 +10018,13 @@
boolean canUseManagedServices(String pkg, Integer userId, String requiredPermission) {
boolean canUseManagedServices = true;
if (requiredPermission != null) {
- if (mPermissionInternal.checkPermission(requiredPermission, pkg, userId)
- != PackageManager.PERMISSION_GRANTED) {
- canUseManagedServices = false;
+ try {
+ if (mPackageManager.checkPermission(requiredPermission, pkg, userId)
+ != PackageManager.PERMISSION_GRANTED) {
+ canUseManagedServices = false;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "can't talk to pm", e);
}
}
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 2fdc4cd..e698c4b 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -16,6 +16,7 @@
package com.android.server.os;
+import android.Manifest;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
@@ -25,6 +26,7 @@
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Binder;
+import android.os.BugreportManager.BugreportCallback;
import android.os.BugreportParams;
import android.os.IDumpstate;
import android.os.IDumpstateListener;
@@ -35,10 +37,13 @@
import android.os.UserHandle;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemConfig;
import java.io.FileDescriptor;
@@ -60,18 +65,105 @@
private final AppOpsManager mAppOps;
private final TelephonyManager mTelephonyManager;
private final ArraySet<String> mBugreportWhitelistedPackages;
+ private final BugreportFileManager mBugreportFileManager;
@GuardedBy("mLock")
private OptionalInt mPreDumpedDataUid = OptionalInt.empty();
- BugreportManagerServiceImpl(Context context) {
- mContext = context;
- mAppOps = context.getSystemService(AppOpsManager.class);
- mTelephonyManager = context.getSystemService(TelephonyManager.class);
- mBugreportWhitelistedPackages =
- SystemConfig.getInstance().getBugreportWhitelistedPackages();
+ /** Helper class for associating previously generated bugreports with their callers. */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ static class BugreportFileManager {
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final ArrayMap<Pair<Integer, String>, ArraySet<String>> mBugreportFiles;
+
+ BugreportFileManager() {
+ mBugreportFiles = new ArrayMap<>();
+ }
+
+ /**
+ * Checks that a given file was generated on behalf of the given caller. If the file was
+ * generated on behalf of the caller, it is removed from the bugreport mapping so that it
+ * may not be retrieved again. If the file was not generated on behalf of the caller, an
+ * {@link IllegalArgumentException} is thrown.
+ *
+ * @param callingInfo a (uid, package name) pair identifying the caller
+ * @param bugreportFile the file name which was previously given to the caller in the
+ * {@link BugreportCallback#onFinished(String)} callback.
+ *
+ * @throws IllegalArgumentException if {@code bugreportFile} is not associated with
+ * {@code callingInfo}.
+ */
+ void ensureCallerPreviouslyGeneratedFile(
+ Pair<Integer, String> callingInfo, String bugreportFile) {
+ synchronized (mLock) {
+ ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(callingInfo);
+ if (bugreportFilesForCaller != null
+ && bugreportFilesForCaller.contains(bugreportFile)) {
+ bugreportFilesForCaller.remove(bugreportFile);
+ if (bugreportFilesForCaller.isEmpty()) {
+ mBugreportFiles.remove(callingInfo);
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "File " + bugreportFile + " was not generated"
+ + " on behalf of calling package " + callingInfo.second);
+ }
+ }
+ }
+
+ /**
+ * Associates a bugreport file with a caller, which is identified as a
+ * (uid, package name) pair.
+ */
+ void addBugreportFileForCaller(Pair<Integer, String> caller, String bugreportFile) {
+ synchronized (mLock) {
+ if (!mBugreportFiles.containsKey(caller)) {
+ mBugreportFiles.put(caller, new ArraySet<>());
+ }
+ ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(caller);
+ bugreportFilesForCaller.add(bugreportFile);
+ }
+ }
}
+ static class Injector {
+ Context mContext;
+ ArraySet<String> mAllowlistedPackages;
+
+ Injector(Context context, ArraySet<String> allowlistedPackages) {
+ mContext = context;
+ mAllowlistedPackages = allowlistedPackages;
+ }
+
+ Context getContext() {
+ return mContext;
+ }
+
+ ArraySet<String> getAllowlistedPackages() {
+ return mAllowlistedPackages;
+ }
+
+ }
+
+ BugreportManagerServiceImpl(Context context) {
+ this(new Injector(context, SystemConfig.getInstance().getBugreportWhitelistedPackages()));
+
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ BugreportManagerServiceImpl(Injector injector) {
+ mContext = injector.getContext();
+ mAppOps = mContext.getSystemService(AppOpsManager.class);
+ mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+ mBugreportFileManager = new BugreportFileManager();
+ mBugreportWhitelistedPackages =
+ injector.getAllowlistedPackages();
+ }
+
+
@Override
@RequiresPermission(android.Manifest.permission.DUMP)
public void preDumpUiData(String callingPackage) {
@@ -135,6 +227,50 @@
}
}
+ @Override
+ @RequiresPermission(Manifest.permission.DUMP)
+ public void retrieveBugreport(int callingUidUnused, String callingPackage,
+ FileDescriptor bugreportFd, String bugreportFile, IDumpstateListener listener) {
+ int callingUid = Binder.getCallingUid();
+ enforcePermission(callingPackage, callingUid, false);
+
+ try {
+ mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
+ new Pair<>(callingUid, callingPackage), bugreportFile);
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, e.getMessage());
+ reportError(listener, IDumpstateListener.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
+ return;
+ }
+
+ synchronized (mLock) {
+ if (isDumpstateBinderServiceRunningLocked()) {
+ Slog.w(TAG, "'dumpstate' is already running. Cannot retrieve a bugreport"
+ + " while another one is currently in progress.");
+ reportError(listener,
+ IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
+ return;
+ }
+
+ IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
+ if (ds == null) {
+ Slog.w(TAG, "Unable to get bugreport service");
+ reportError(listener, IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR);
+ return;
+ }
+
+ // Wrap the listener so we can intercept binder events directly.
+ IDumpstateListener myListener = new DumpstateListener(listener, ds,
+ new Pair<>(callingUid, callingPackage));
+ try {
+ ds.retrieveBugreport(callingUid, callingPackage, bugreportFd,
+ bugreportFile, myListener);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException in retrieveBugreport", e);
+ }
+ }
+ }
+
private void validateBugreportMode(@BugreportParams.BugreportMode int mode) {
if (mode != BugreportParams.BUGREPORT_MODE_FULL
&& mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE
@@ -148,7 +284,9 @@
}
private void validateBugreportFlags(int flags) {
- flags = clearBugreportFlag(flags, BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA);
+ flags = clearBugreportFlag(flags,
+ BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA
+ | BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT);
if (flags != 0) {
Slog.w(TAG, "Unknown bugreport flags: " + flags);
throw new IllegalArgumentException("Unknown bugreport flags: " + flags);
@@ -298,6 +436,9 @@
}
}
+ boolean isConsentDeferred =
+ (bugreportFlags & BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT) != 0;
+
IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
if (ds == null) {
Slog.w(TAG, "Unable to get bugreport service");
@@ -306,7 +447,8 @@
}
// Wrap the listener so we can intercept binder events directly.
- IDumpstateListener myListener = new DumpstateListener(listener, ds);
+ IDumpstateListener myListener = new DumpstateListener(listener, ds,
+ isConsentDeferred ? new Pair<>(callingUid, callingPackage) : null);
try {
ds.startBugreport(callingUid, callingPackage, bugreportFd, screenshotFd, bugreportMode,
bugreportFlags, myListener, isScreenshotRequested);
@@ -405,10 +547,13 @@
private final IDumpstateListener mListener;
private final IDumpstate mDs;
private boolean mDone = false;
+ private final Pair<Integer, String> mCaller;
- DumpstateListener(IDumpstateListener listener, IDumpstate ds) {
+ DumpstateListener(IDumpstateListener listener, IDumpstate ds,
+ @Nullable Pair<Integer, String> caller) {
mListener = listener;
mDs = ds;
+ mCaller = caller;
try {
mDs.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -430,11 +575,14 @@
}
@Override
- public void onFinished() throws RemoteException {
+ public void onFinished(String bugreportFile) throws RemoteException {
synchronized (mLock) {
mDone = true;
}
- mListener.onFinished();
+ if (mCaller != null) {
+ mBugreportFileManager.addBugreportFileForCaller(mCaller, bugreportFile);
+ }
+ mListener.onFinished(bugreportFile);
}
@Override
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 69436da..8d40adf 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -27,12 +27,15 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
+import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
@@ -51,6 +54,7 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
@@ -122,7 +126,18 @@
@Override
public ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
@PackageManager.PackageInfoFlagsBits long flags, int userId) {
- return mService.getBackgroundInstalledPackages(flags, userId);
+ if (!Build.IS_DEBUGGABLE) {
+ return mService.getBackgroundInstalledPackages(flags, userId);
+ }
+ // The debug.transparency.bg-install-apps (only works for debuggable builds)
+ // is used to set mock list of background installed apps for testing.
+ // The list of apps' names is delimited by ",".
+ String propertyString = SystemProperties.get("debug.transparency.bg-install-apps");
+ if (TextUtils.isEmpty(propertyString)) {
+ return mService.getBackgroundInstalledPackages(flags, userId);
+ } else {
+ return mService.getMockBackgroundInstalledPackages(propertyString);
+ }
}
}
@@ -145,6 +160,27 @@
return new ParceledListSlice<>(packages);
}
+ /**
+ * Mock a list of background installed packages based on the property string.
+ */
+ @NonNull
+ ParceledListSlice<PackageInfo> getMockBackgroundInstalledPackages(
+ @NonNull String propertyString) {
+ String[] mockPackageNames = propertyString.split(",");
+ List<PackageInfo> mockPackages = new ArrayList<>();
+ for (String name : mockPackageNames) {
+ try {
+ PackageInfo packageInfo = mPackageManager.getPackageInfo(name,
+ PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL));
+ mockPackages.add(packageInfo);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Package's PackageInfo not found " + name);
+ continue;
+ }
+ }
+ return new ParceledListSlice<>(mockPackages);
+ }
+
private static class EventHandler extends Handler {
private final BackgroundInstallControlService mService;
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 094b182..58a609e 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -1856,8 +1856,7 @@
// Figure out which lib versions the caller can see
LongSparseLongArray versionsCallerCanSee = null;
final int callingAppId = UserHandle.getAppId(callingUid);
- if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.SHELL_UID
- && callingAppId != Process.ROOT_UID) {
+ if (!PackageManagerServiceUtils.isSystemOrRootOrShell(callingAppId)) {
versionsCallerCanSee = new LongSparseLongArray();
String libName = versionedLib.valueAt(0).getName();
String[] uidPackages = getPackagesForUidInternal(callingUid, callingUid);
@@ -2034,8 +2033,7 @@
if ((flags & PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES) != 0) {
// System/shell/root get to see all static libs
final int appId = UserHandle.getAppId(uid);
- if (appId == Process.SYSTEM_UID || appId == Process.SHELL_UID
- || appId == Process.ROOT_UID) {
+ if (PackageManagerServiceUtils.isSystemOrRootOrShell(appId)) {
return false;
}
// Installer gets to see all static libs.
@@ -2091,8 +2089,7 @@
if ((flags & PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES) != 0) {
// System/shell/root get to see all SDK libs.
final int appId = UserHandle.getAppId(uid);
- if (appId == Process.SYSTEM_UID || appId == Process.SHELL_UID
- || appId == Process.ROOT_UID) {
+ if (PackageManagerServiceUtils.isSystemOrRootOrShell(appId)) {
return false;
}
// Installer gets to see all SDK libs.
@@ -2152,7 +2149,7 @@
if (!requirePermissionWhenSameUser && userId == callingUserId) {
return true;
}
- if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) {
+ if (PackageManagerServiceUtils.isSystemOrRoot(callingUid)) {
return true;
}
if (requireFullPermission) {
@@ -3813,8 +3810,7 @@
public boolean canRequestPackageInstalls(@NonNull String packageName, int callingUid,
int userId, boolean throwIfPermNotDeclared) {
int uid = getPackageUidInternal(packageName, 0, userId, callingUid);
- if (callingUid != uid && callingUid != Process.ROOT_UID
- && callingUid != Process.SYSTEM_UID) {
+ if (callingUid != uid && !PackageManagerServiceUtils.isSystemOrRoot(callingUid)) {
throw new SecurityException(
"Caller uid " + callingUid + " does not own package " + packageName);
}
@@ -5540,8 +5536,8 @@
enforceCrossUserPermission(callingUid, userId, true /*requireFullPermission*/,
true /*checkShell*/, "getHarmfulAppInfo");
- if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.ROOT_UID &&
- checkUidPermission(SET_HARMFUL_APP_WARNINGS, callingUid) != PERMISSION_GRANTED) {
+ if (!PackageManagerServiceUtils.isSystemOrRoot(callingAppId)
+ && checkUidPermission(SET_HARMFUL_APP_WARNINGS, callingUid) != PERMISSION_GRANTED) {
throw new SecurityException("Caller must have the "
+ SET_HARMFUL_APP_WARNINGS + " permission.");
}
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index e0de294..225d2a4 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -840,7 +840,7 @@
private boolean isCallerAllowedToSilentlyUninstall(@NonNull Computer snapshot, int callingUid,
String pkgName, int userId) {
- if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID
+ if (PackageManagerServiceUtils.isRootOrShell(callingUid)
|| UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) {
return true;
}
diff --git a/services/core/java/com/android/server/pm/GentleUpdateHelper.java b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
index ae80bab..babcd33 100644
--- a/services/core/java/com/android/server/pm/GentleUpdateHelper.java
+++ b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
@@ -145,7 +145,7 @@
mHandler.post(() -> {
var pendingCheck = new PendingInstallConstraintsCheck(
packageNames, constraints, resultFuture, timeoutMillis);
- var deviceIdleFuture = constraints.isRequireDeviceIdle()
+ var deviceIdleFuture = constraints.isDeviceIdleRequired()
? checkDeviceIdle() : CompletableFuture.completedFuture(false);
deviceIdleFuture.thenAccept(isIdle -> {
Preconditions.checkState(mHandler.getLooper().isCurrentThread());
@@ -217,14 +217,14 @@
@WorkerThread
private boolean areConstraintsSatisfied(List<String> packageNames,
InstallConstraints constraints, boolean isIdle) {
- return (!constraints.isRequireDeviceIdle() || isIdle)
- && (!constraints.isRequireAppNotForeground()
+ return (!constraints.isDeviceIdleRequired() || isIdle)
+ && (!constraints.isAppNotForegroundRequired()
|| !mAppStateHelper.hasForegroundApp(packageNames))
- && (!constraints.isRequireAppNotInteracting()
+ && (!constraints.isAppNotInteractingRequired()
|| !mAppStateHelper.hasInteractingApp(packageNames))
- && (!constraints.isRequireAppNotTopVisible()
+ && (!constraints.isAppNotTopVisibleRequired()
|| !mAppStateHelper.hasTopVisibleApp(packageNames))
- && (!constraints.isRequireNotInCall()
+ && (!constraints.isNotInCallRequired()
|| !mAppStateHelper.isInCall());
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 4808d4c..1ed5999 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -70,6 +70,7 @@
import static com.android.server.pm.PackageManagerService.SCAN_AS_OEM;
import static com.android.server.pm.PackageManagerService.SCAN_AS_PRIVILEGED;
import static com.android.server.pm.PackageManagerService.SCAN_AS_PRODUCT;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_STOPPED_SYSTEM_APP;
import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM;
import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM_EXT;
import static com.android.server.pm.PackageManagerService.SCAN_AS_VENDOR;
@@ -100,6 +101,7 @@
import android.apex.ApexInfo;
import android.app.AppOpsManager;
import android.app.ApplicationPackageManager;
+import android.app.BroadcastOptions;
import android.app.backup.IBackupManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -686,7 +688,10 @@
fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
PackageManager.installStatusToPublicStatus(returnCode));
try {
- target.sendIntent(context, 0, fillIn, null, null);
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+ target.sendIntent(context, 0, fillIn, null /* onFinished*/, null /* handler */,
+ null /* requiredPermission */, options.toBundle());
} catch (IntentSender.SendIntentException ignored) {
}
}
@@ -1171,7 +1176,8 @@
request.setName(pkgName);
if (parsedPackage.isTestOnly()) {
if ((installFlags & PackageManager.INSTALL_ALLOW_TEST) == 0) {
- throw new PrepareFailure(INSTALL_FAILED_TEST_ONLY, "installPackageLI");
+ throw new PrepareFailure(INSTALL_FAILED_TEST_ONLY,
+ "Failed to install test-only apk. Did you forget to add -t?");
}
}
@@ -4236,6 +4242,18 @@
}
}
+ // A new application appeared on /system, and we are seeing it for the first time.
+ // Its also not updated as we don't have a copy of it on /data. So, scan it in a
+ // STOPPED state. Ignore if it's an APEX package since stopped state does not affect them.
+ final boolean isApexPkg = (scanFlags & SCAN_AS_APEX) != 0;
+ if (mPm.mShouldStopSystemPackagesByDefault && scanSystemPartition
+ && !pkgAlreadyExists && !isApexPkg) {
+ String packageName = parsedPackage.getPackageName();
+ if (!mPm.mInitialNonStoppedSystemPackages.contains(packageName)) {
+ scanFlags |= SCAN_AS_STOPPED_SYSTEM_APP;
+ }
+ }
+
final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags,
scanFlags | SCAN_UPDATE_SIGNATURE, 0 /* currentTime */, user, null);
return new Pair<>(scanResult, shouldHideSystemApp);
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 7b759e3..43c0e05 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -525,13 +525,13 @@
}
private void processApkInstallRequests(boolean success, List<InstallRequest> installRequests) {
- if (success) {
+ if (!success) {
for (InstallRequest request : installRequests) {
if (request.getReturnCode() != PackageManager.INSTALL_SUCCEEDED) {
cleanUpForFailedInstall(request);
}
}
-
+ } else {
mInstallPackageHelper.installPackagesTraced(installRequests);
for (InstallRequest request : installRequests) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 617561a..8823714 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -28,6 +28,7 @@
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PackageDeleteObserver;
@@ -663,7 +664,7 @@
&& params.installerPackageName.length() < SessionParams.MAX_PACKAGE_NAME_LENGTH)
? params.installerPackageName : installerPackageName;
- if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)
+ if (PackageManagerServiceUtils.isRootOrShell(callingUid)
|| PackageInstallerSession.isSystemDataLoaderInstallation(params)) {
params.installFlags |= PackageManager.INSTALL_FROM_ADB;
// adb installs can override the installingPackageName, but not the
@@ -706,7 +707,7 @@
}
}
- if (Build.IS_DEBUGGABLE || isCalledBySystemOrShell(callingUid)) {
+ if (Build.IS_DEBUGGABLE || PackageManagerServiceUtils.isSystemOrRoot(callingUid)) {
params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
} else {
params.installFlags &= ~PackageManager.INSTALL_ALLOW_DOWNGRADE;
@@ -748,7 +749,8 @@
if (params.isMultiPackage) {
throw new IllegalArgumentException("A multi-session can't be set as APEX.");
}
- if (isCalledBySystemOrShell(callingUid) || mBypassNextAllowedApexUpdateCheck) {
+ if (PackageManagerServiceUtils.isSystemOrRootOrShell(callingUid)
+ || mBypassNextAllowedApexUpdateCheck) {
params.installFlags |= PackageManager.INSTALL_DISABLE_ALLOWED_APEX_UPDATE_CHECK;
} else {
// Only specific APEX updates (installed through ADB, or for CTS tests) can disable
@@ -758,20 +760,20 @@
}
if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0
- && !isCalledBySystemOrShell(callingUid)
+ && !PackageManagerServiceUtils.isSystemOrRootOrShell(callingUid)
&& (snapshot.getFlagsForUid(callingUid) & ApplicationInfo.FLAG_SYSTEM)
== 0) {
throw new SecurityException(
"Only system apps could use the PackageManager.INSTALL_INSTANT_APP flag.");
}
- if (params.isStaged && !isCalledBySystemOrShell(callingUid)) {
+ if (params.isStaged && !PackageManagerServiceUtils.isSystemOrRootOrShell(callingUid)) {
if (!mBypassNextStagedInstallerCheck
&& !isStagedInstallerAllowed(requestedInstallerPackageName)) {
throw new SecurityException("Installer not allowed to commit staged install");
}
}
- if (isApex && !isCalledBySystemOrShell(callingUid)) {
+ if (isApex && !PackageManagerServiceUtils.isSystemOrRootOrShell(callingUid)) {
if (!mBypassNextStagedInstallerCheck
&& !isStagedInstallerAllowed(requestedInstallerPackageName)) {
throw new SecurityException(
@@ -874,7 +876,7 @@
// reset the force queryable param if it's not called by an approved caller.
if (params.forceQueryableOverride) {
- if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID) {
+ if (!PackageManagerServiceUtils.isRootOrShell(callingUid)) {
params.forceQueryableOverride = false;
}
}
@@ -916,11 +918,6 @@
return sessionId;
}
- private boolean isCalledBySystemOrShell(int callingUid) {
- return callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID
- || callingUid == Process.SHELL_UID;
- }
-
private boolean isStagedInstallerAllowed(String installerName) {
return SystemConfig.getInstance().getWhitelistedStagedInstallers().contains(installerName);
}
@@ -1185,7 +1182,7 @@
final Computer snapshot = mPm.snapshotComputer();
final int callingUid = Binder.getCallingUid();
snapshot.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
- if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) {
+ if (!PackageManagerServiceUtils.isRootOrShell(callingUid)) {
mAppOps.checkPackage(callingUid, callerPackageName);
}
@@ -1239,7 +1236,7 @@
mContext.enforceCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES, null);
final Computer snapshot = mPm.snapshotComputer();
snapshot.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
- if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) {
+ if (!PackageManagerServiceUtils.isRootOrShell(callingUid)) {
mAppOps.checkPackage(callingUid, callerPackageName);
}
@@ -1276,7 +1273,7 @@
final var snapshot = mPm.snapshotComputer();
final int callingUid = Binder.getCallingUid();
- if (!isCalledBySystemOrShell(callingUid)) {
+ if (!PackageManagerServiceUtils.isSystemOrRootOrShell(callingUid)) {
for (var packageName : packageNames) {
var ps = snapshot.getPackageStateInternal(packageName);
if (ps == null || !TextUtils.equals(
@@ -1318,7 +1315,10 @@
intent.putExtra(PackageInstaller.EXTRA_INSTALL_CONSTRAINTS, constraints);
intent.putExtra(PackageInstaller.EXTRA_INSTALL_CONSTRAINTS_RESULT, result);
try {
- callback.sendIntent(mContext, 0, intent, null, null);
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+ callback.sendIntent(mContext, 0, intent, null /* onFinished*/,
+ null /* handler */, null /* requiredPermission */, options.toBundle());
} catch (SendIntentException ignore) {
}
});
@@ -1363,7 +1363,7 @@
@Override
public void bypassNextStagedInstallerCheck(boolean value) {
- if (!isCalledBySystemOrShell(Binder.getCallingUid())) {
+ if (!PackageManagerServiceUtils.isSystemOrRootOrShell(Binder.getCallingUid())) {
throw new SecurityException("Caller not allowed to bypass staged installer check");
}
mBypassNextStagedInstallerCheck = value;
@@ -1371,7 +1371,7 @@
@Override
public void bypassNextAllowedApexUpdateCheck(boolean value) {
- if (!isCalledBySystemOrShell(Binder.getCallingUid())) {
+ if (!PackageManagerServiceUtils.isSystemOrRootOrShell(Binder.getCallingUid())) {
throw new SecurityException("Caller not allowed to bypass allowed apex update check");
}
mBypassNextAllowedApexUpdateCheck = value;
@@ -1379,7 +1379,7 @@
@Override
public void disableVerificationForUid(int uid) {
- if (!isCalledBySystemOrShell(Binder.getCallingUid())) {
+ if (!PackageManagerServiceUtils.isSystemOrRootOrShell(Binder.getCallingUid())) {
throw new SecurityException("Operation not allowed for caller");
}
mDisableVerificationForUid = uid;
@@ -1390,7 +1390,7 @@
*/
@Override
public void setAllowUnlimitedSilentUpdates(@Nullable String installerPackageName) {
- if (!isCalledBySystemOrShell(Binder.getCallingUid())) {
+ if (!PackageManagerServiceUtils.isSystemOrRootOrShell(Binder.getCallingUid())) {
throw new SecurityException("Caller not allowed to unlimite silent updates");
}
mSilentUpdatePolicy.setAllowUnlimitedSilentUpdates(installerPackageName);
@@ -1401,7 +1401,7 @@
*/
@Override
public void setSilentUpdatesThrottleTime(long throttleTimeInSeconds) {
- if (!isCalledBySystemOrShell(Binder.getCallingUid())) {
+ if (!PackageManagerServiceUtils.isSystemOrRootOrShell(Binder.getCallingUid())) {
throw new SecurityException("Caller not allowed to set silent updates throttle time");
}
mSilentUpdatePolicy.setSilentUpdatesThrottleTime(throttleTimeInSeconds);
@@ -1481,7 +1481,10 @@
PackageInstaller.STATUS_PENDING_USER_ACTION);
fillIn.putExtra(Intent.EXTRA_INTENT, intent);
try {
- mTarget.sendIntent(mContext, 0, fillIn, null, null);
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+ mTarget.sendIntent(mContext, 0, fillIn, null /* onFinished*/,
+ null /* handler */, null /* requiredPermission */, options.toBundle());
} catch (SendIntentException ignored) {
}
}
@@ -1506,7 +1509,10 @@
PackageManager.deleteStatusToString(returnCode, msg));
fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode);
try {
- mTarget.sendIntent(mContext, 0, fillIn, null, null);
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+ mTarget.sendIntent(mContext, 0, fillIn, null /* onFinished*/,
+ null /* handler */, null /* requiredPermission */, options.toBundle());
} catch (SendIntentException ignored) {
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 47e18f1..f77d38f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -59,6 +59,7 @@
import android.annotation.Nullable;
import android.annotation.WorkerThread;
import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.admin.DevicePolicyEventLogger;
@@ -4495,8 +4496,10 @@
intent.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_SUCCESS);
intent.putExtra(PackageInstaller.EXTRA_PRE_APPROVAL, true);
try {
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityLaunchAllowed(false);
target.sendIntent(mContext, 0 /* code */, intent, null /* onFinished */,
- null /* handler */);
+ null /* handler */, null /* requiredPermission */, options.toBundle());
} catch (IntentSender.SendIntentException ignored) {
}
}
@@ -4763,7 +4766,10 @@
PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(intent.getAction()));
fillIn.putExtra(Intent.EXTRA_INTENT, intent);
try {
- target.sendIntent(context, 0, fillIn, null, null);
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+ target.sendIntent(context, 0, fillIn, null /* onFinished */,
+ null /* handler */, null /* requiredPermission */, options.toBundle());
} catch (IntentSender.SendIntentException ignored) {
}
}
@@ -4805,7 +4811,10 @@
}
}
try {
- target.sendIntent(context, 0, fillIn, null, null);
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+ target.sendIntent(context, 0, fillIn, null /* onFinished */,
+ null /* handler */, null /* requiredPermission */, options.toBundle());
} catch (IntentSender.SendIntentException ignored) {
}
}
@@ -4839,7 +4848,10 @@
intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, "Staging Image Not Ready");
}
try {
- target.sendIntent(context, 0, intent, null, null);
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+ target.sendIntent(context, 0, intent, null /* onFinished */,
+ null /* handler */, null /* requiredPermission */, options.toBundle());
} catch (IntentSender.SendIntentException ignored) {
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a02a419..d0a0558 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -56,6 +56,7 @@
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.ApplicationPackageManager;
+import android.app.BroadcastOptions;
import android.app.IActivityManager;
import android.app.admin.IDevicePolicyManager;
import android.app.admin.SecurityLog;
@@ -387,6 +388,7 @@
static final int SCAN_DROP_CACHE = 1 << 24;
static final int SCAN_AS_FACTORY = 1 << 25;
static final int SCAN_AS_APEX = 1 << 26;
+ static final int SCAN_AS_STOPPED_SYSTEM_APP = 1 << 27;
@IntDef(flag = true, prefix = { "SCAN_" }, value = {
SCAN_NO_DEX,
@@ -403,6 +405,7 @@
SCAN_AS_INSTANT_APP,
SCAN_AS_FULL_APP,
SCAN_AS_VIRTUAL_PRELOAD,
+ SCAN_AS_STOPPED_SYSTEM_APP,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ScanFlags {}
@@ -964,6 +967,8 @@
final @Nullable String mRecentsPackage;
final @Nullable String mAmbientContextDetectionPackage;
final @Nullable String mWearableSensingPackage;
+ final @NonNull Set<String> mInitialNonStoppedSystemPackages;
+ final boolean mShouldStopSystemPackagesByDefault;
private final @NonNull String mRequiredSdkSandboxPackage;
@GuardedBy("mLock")
@@ -1771,6 +1776,8 @@
mOverlayConfigSignaturePackage = testParams.overlayConfigSignaturePackage;
mResolveComponentName = testParams.resolveComponentName;
mRequiredSdkSandboxPackage = testParams.requiredSdkSandboxPackage;
+ mInitialNonStoppedSystemPackages = testParams.initialNonStoppedSystemPackages;
+ mShouldStopSystemPackagesByDefault = testParams.shouldStopSystemPackagesByDefault;
mLiveComputer = createLiveComputer();
mSnapshotStatistics = null;
@@ -2097,6 +2104,11 @@
mCacheDir = PackageManagerServiceUtils.preparePackageParserCache(
mIsEngBuild, mIsUserDebugBuild, mIncrementalVersion);
+ mInitialNonStoppedSystemPackages = mInjector.getSystemConfig()
+ .getInitialNonStoppedSystemPackages();
+ mShouldStopSystemPackagesByDefault = mContext.getResources()
+ .getBoolean(R.bool.config_stopSystemPackagesByDefault);
+
final int[] userIds = mUserManager.getUserIds();
PackageParser2 packageParser = mInjector.getScanningCachingPackageParser();
mOverlayConfig = mInitAppsHelper.initSystemApps(packageParser, packageSettings, userIds,
@@ -3201,7 +3213,7 @@
mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
callingMethod);
- if (callingUid != Process.ROOT_UID && callingUid != Process.SYSTEM_UID
+ if (!PackageManagerServiceUtils.isSystemOrRoot(callingUid)
&& UserHandle.getUserId(callingUid) != userId) {
throw new SecurityException("Calling uid " + callingUid + " cannot call for user "
+ userId);
@@ -4937,7 +4949,11 @@
}
if (pi != null) {
try {
- pi.sendIntent(null, success ? 1 : 0, null, null, null);
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+ pi.sendIntent(null, success ? 1 : 0, null /* intent */,
+ null /* onFinished*/, null /* handler */,
+ null /* requiredPermission */, options.toBundle());
} catch (SendIntentException e) {
Slog.w(TAG, e);
}
@@ -5312,7 +5328,7 @@
snapshot.enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
true /*checkShell*/, "isPackageStateProtected");
- if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.ROOT_UID
+ if (!PackageManagerServiceUtils.isSystemOrRoot(callingAppId)
&& snapshot.checkUidPermission(MANAGE_DEVICE_ADMINS, callingUid)
!= PERMISSION_GRANTED) {
throw new SecurityException("Caller must have the "
@@ -5847,8 +5863,8 @@
snapshot.enforceCrossUserPermission(callingUid, userId, true /*requireFullPermission*/,
true /*checkShell*/, "setHarmfulAppInfo");
- if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.ROOT_UID &&
- snapshot.checkUidPermission(SET_HARMFUL_APP_WARNINGS, callingUid)
+ if (!PackageManagerServiceUtils.isSystemOrRoot(callingAppId)
+ && snapshot.checkUidPermission(SET_HARMFUL_APP_WARNINGS, callingUid)
!= PERMISSION_GRANTED) {
throw new SecurityException("Caller must have the "
+ SET_HARMFUL_APP_WARNINGS + " permission.");
@@ -6602,7 +6618,7 @@
public void uninstallApex(String packageName, long versionCode, int userId,
IntentSender intentSender, int flags) {
final int callerUid = Binder.getCallingUid();
- if (callerUid != Process.ROOT_UID && callerUid != Process.SHELL_UID) {
+ if (!PackageManagerServiceUtils.isRootOrShell(callerUid)) {
throw new SecurityException("Not allowed to uninstall apexes");
}
PackageInstallerService.PackageDeleteObserverAdapter adapter =
@@ -6647,7 +6663,7 @@
final int callingUid = Binder.getCallingUid();
final Computer snapshot = snapshotComputer();
final String[] callerPackageNames = snapshot.getPackagesForUid(callingUid);
- if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID
+ if (!PackageManagerServiceUtils.isRootOrShell(callingUid)
&& !ArrayUtils.contains(callerPackageNames, packageName)) {
throw new SecurityException("dumpProfiles");
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index e5cfa67..0ed90e4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -26,6 +26,7 @@
import android.os.Handler;
import android.os.incremental.IncrementalManager;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.DisplayMetrics;
import com.android.internal.annotations.VisibleForTesting;
@@ -40,6 +41,7 @@
import java.io.File;
import java.util.List;
+import java.util.Set;
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
public final class PackageManagerServiceTestParams {
@@ -119,4 +121,6 @@
public SuspendPackageHelper suspendPackageHelper;
public DistractingPackageHelper distractingPackageHelper;
public StorageEventHelper storageEventHelper;
+ public Set<String> initialNonStoppedSystemPackages = new ArraySet<>();
+ public boolean shouldStopSystemPackagesByDefault;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index b919330..928ffa7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -1372,10 +1372,24 @@
*/
public static boolean isSystemOrRoot() {
final int uid = Binder.getCallingUid();
+ return isSystemOrRoot(uid);
+ }
+
+ /**
+ * Check if a UID is system UID or root's UID.
+ */
+ public static boolean isSystemOrRoot(int uid) {
return uid == Process.SYSTEM_UID || uid == Process.ROOT_UID;
}
/**
+ * Check if a UID is system UID or shell's UID.
+ */
+ public static boolean isRootOrShell(int uid) {
+ return uid == Process.ROOT_UID || uid == Process.SHELL_UID;
+ }
+
+ /**
* Enforces that only the system UID or root's UID can call a method exposed
* via Binder.
*
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 830b096c..2538871 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -31,6 +31,7 @@
import static com.android.server.pm.PackageManagerService.SCAN_AS_OEM;
import static com.android.server.pm.PackageManagerService.SCAN_AS_PRIVILEGED;
import static com.android.server.pm.PackageManagerService.SCAN_AS_PRODUCT;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_STOPPED_SYSTEM_APP;
import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM;
import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM_EXT;
import static com.android.server.pm.PackageManagerService.SCAN_AS_VENDOR;
@@ -198,6 +199,7 @@
if (createNewPackage) {
final boolean instantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
final boolean virtualPreload = (scanFlags & SCAN_AS_VIRTUAL_PRELOAD) != 0;
+ final boolean isStoppedSystemApp = (scanFlags & SCAN_AS_STOPPED_SYSTEM_APP) != 0;
// Flags contain system values stored in the server variant of AndroidPackage,
// and so the server-side PackageInfoUtils is still called, even without a
@@ -212,7 +214,7 @@
AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage),
AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage),
parsedPackage.getLongVersionCode(), pkgFlags, pkgPrivateFlags, user,
- true /*allowInstall*/, instantApp, virtualPreload,
+ true /*allowInstall*/, instantApp, virtualPreload, isStoppedSystemApp,
UserManagerService.getInstance(), usesSdkLibraries,
parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries,
parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 165e476..9c91879 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -89,7 +89,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
-import com.android.internal.security.VerityUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.IndentingPrintWriter;
@@ -121,6 +120,7 @@
import com.android.server.pm.verify.domain.DomainVerificationLegacySettings;
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.verify.domain.DomainVerificationPersistence;
+import com.android.server.security.FileIntegrityLocal;
import com.android.server.utils.Slogf;
import com.android.server.utils.Snappable;
import com.android.server.utils.SnapshotCache;
@@ -1041,7 +1041,7 @@
File codePath, String legacyNativeLibraryPath, String primaryCpuAbi,
String secondaryCpuAbi, long versionCode, int pkgFlags, int pkgPrivateFlags,
UserHandle installUser, boolean allowInstall, boolean instantApp,
- boolean virtualPreload, UserManagerService userManager,
+ boolean virtualPreload, boolean isStoppedSystemApp, UserManagerService userManager,
String[] usesSdkLibraries, long[] usesSdkLibrariesVersions,
String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
Set<String> mimeGroupNames, @NonNull UUID domainSetId) {
@@ -1068,6 +1068,9 @@
pkgSetting.setFlags(pkgFlags)
.setPrivateFlags(pkgPrivateFlags);
} else {
+ int installUserId = installUser != null ? installUser.getIdentifier()
+ : UserHandle.USER_SYSTEM;
+
pkgSetting = new PackageSetting(pkgName, realPkgName, codePath,
legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi,
null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags,
@@ -1086,8 +1089,6 @@
Slog.i(PackageManagerService.TAG, "Stopping package " + pkgName, e);
}
List<UserInfo> users = getAllUsers(userManager);
- int installUserId = installUser != null ? installUser.getIdentifier()
- : UserHandle.USER_SYSTEM;
if (users != null && allowInstall) {
for (UserInfo user : users) {
// By default we consider this app to be installed
@@ -1126,6 +1127,13 @@
);
}
}
+ } else if (isStoppedSystemApp) {
+ if (DEBUG_STOPPED) {
+ RuntimeException e = new RuntimeException("here");
+ e.fillInStackTrace();
+ Slog.i(PackageManagerService.TAG, "Stopping system package " + pkgName, e);
+ }
+ pkgSetting.setStopped(true, installUserId);
}
if (sharedUser != null) {
pkgSetting.setAppId(sharedUser.mAppId);
@@ -2706,8 +2714,8 @@
}
try {
- VerityUtils.setUpFsverity(mSettingsFilename.getAbsolutePath());
- VerityUtils.setUpFsverity(mSettingsReserveCopyFilename.getAbsolutePath());
+ FileIntegrityLocal.setUpFsVerity(mSettingsFilename.getAbsolutePath());
+ FileIntegrityLocal.setUpFsVerity(mSettingsReserveCopyFilename.getAbsolutePath());
} catch (IOException e) {
Slog.e(TAG, "Failed to verity-protect settings", e);
}
@@ -4422,6 +4430,15 @@
ps.getPackageName()));
// Only system apps are initially installed.
ps.setInstalled(shouldReallyInstall, userHandle);
+
+ // Non-Apex system apps, that are not included in the allowlist in
+ // initialNonStoppedSystemPackages, should be marked as stopped by default.
+ final boolean shouldBeStopped = service.mShouldStopSystemPackagesByDefault
+ && ps.isSystem()
+ && !ps.isApex()
+ && !service.mInitialNonStoppedSystemPackages.contains(ps.getPackageName());
+ ps.setStopped(shouldBeStopped, userHandle);
+
// If userTypeInstallablePackages is the *only* reason why we're not installing,
// then uninstallReason is USER_TYPE. If there's a different reason, or if we
// actually are installing, put UNKNOWN.
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 0362ddd..6d5e2b0 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -885,7 +885,12 @@
* available ShareTarget definitions in this package.
*/
public List<ShortcutManager.ShareShortcutInfo> getMatchingShareTargets(
- @NonNull IntentFilter filter) {
+ @NonNull final IntentFilter filter) {
+ return getMatchingShareTargets(filter, null);
+ }
+
+ List<ShortcutManager.ShareShortcutInfo> getMatchingShareTargets(
+ @NonNull final IntentFilter filter, @Nullable final String pkgName) {
synchronized (mLock) {
final List<ShareTargetInfo> matchedTargets = new ArrayList<>();
for (int i = 0; i < mShareTargets.size(); i++) {
@@ -909,8 +914,7 @@
// included in the result
findAll(shortcuts, ShortcutInfo::isNonManifestVisible,
ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION,
- mShortcutUser.mService.mContext.getPackageName(),
- 0, /*getPinnedByAnyLauncher=*/ false);
+ pkgName, 0, /*getPinnedByAnyLauncher=*/ false);
final List<ShortcutManager.ShareShortcutInfo> result = new ArrayList<>();
for (int i = 0; i < shortcuts.size(); i++) {
@@ -1108,7 +1112,7 @@
// Now prepare to publish manifest shortcuts.
List<ShortcutInfo> newManifestShortcutList = null;
- final int shareTargetSize;
+ int shareTargetSize = 0;
synchronized (mLock) {
try {
shareTargetSize = mShareTargets.size();
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 83720f1..1cd9ec6 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2512,11 +2512,17 @@
}
enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS,
"getShareTargets");
+ final ComponentName chooser = injectChooserActivity();
+ final String pkg = (chooser != null
+ && mPackageManagerInternal.getComponentEnabledSetting(chooser,
+ injectBinderCallingUid(), userId) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
+ ? chooser.getPackageName() : mContext.getPackageName();
synchronized (mLock) {
throwIfUserLockedL(userId);
final List<ShortcutManager.ShareShortcutInfo> shortcutInfoList = new ArrayList<>();
final ShortcutUser user = getUserShortcutsLocked(userId);
- user.forAllPackages(p -> shortcutInfoList.addAll(p.getMatchingShareTargets(filter)));
+ user.forAllPackages(p -> shortcutInfoList.addAll(
+ p.getMatchingShareTargets(filter, pkg)));
return new ParceledListSlice<>(shortcutInfoList);
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 3c5f309..26a990c 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -52,22 +52,23 @@
// TODO(b/248408342): Move keep annotation to the method referencing these fields reflectively.
@Keep public static final int USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE = 1;
@Keep public static final int USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE = 2;
+ @Keep public static final int USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE = 3;
@Keep public static final int USER_ASSIGNMENT_RESULT_FAILURE = -1;
private static final String PREFIX_USER_ASSIGNMENT_RESULT = "USER_ASSIGNMENT_RESULT_";
@IntDef(flag = false, prefix = {PREFIX_USER_ASSIGNMENT_RESULT}, value = {
USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE,
USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE,
+ USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE,
USER_ASSIGNMENT_RESULT_FAILURE
})
public @interface UserAssignmentResult {}
- // TODO(b/248408342): Move keep annotation to the method referencing these fields reflectively.
- @Keep public static final int USER_START_MODE_FOREGROUND = 1;
- @Keep public static final int USER_START_MODE_BACKGROUND = 2;
- @Keep public static final int USER_START_MODE_BACKGROUND_VISIBLE = 3;
-
private static final String PREFIX_USER_START_MODE = "USER_START_MODE_";
+
+ /**
+ * Type used to indicate how a user started.
+ */
@IntDef(flag = false, prefix = {PREFIX_USER_START_MODE}, value = {
USER_START_MODE_FOREGROUND,
USER_START_MODE_BACKGROUND,
@@ -75,6 +76,32 @@
})
public @interface UserStartMode {}
+ // TODO(b/248408342): Move keep annotations below to the method referencing these fields
+ // reflectively.
+
+ /** (Full) user started on foreground (a.k.a. "current user"). */
+ @Keep public static final int USER_START_MODE_FOREGROUND = 1;
+
+ /**
+ * User (full or profile) started on background and is
+ * {@link UserManager#isUserVisible() invisible}.
+ *
+ * <p>This is the "traditional" way of starting a background user, and can be used to start
+ * profiles as well, although starting an invisible profile is not common from the System UI
+ * (it could be done through APIs or adb, though).
+ */
+ @Keep public static final int USER_START_MODE_BACKGROUND = 2;
+
+ /**
+ * User (full or profile) started on background and is
+ * {@link UserManager#isUserVisible() visible}.
+ *
+ * <p>This is the "traditional" way of starting a profile (i.e., when the profile of the current
+ * user is the current foreground user), but it can also be used to start a full user associated
+ * with a display (which is the case on automotives with passenger displays).
+ */
+ @Keep public static final int USER_START_MODE_BACKGROUND_VISIBLE = 3;
+
public interface UserRestrictionsListener {
/**
* Called when a user restriction changes.
diff --git a/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java b/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java
index 5bdcbb9..50a1d90 100644
--- a/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java
+++ b/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java
@@ -25,6 +25,7 @@
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.os.Binder;
import android.os.Build;
import android.os.Process;
@@ -36,6 +37,7 @@
import android.util.IndentingPrintWriter;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.os.RoSystemProperties;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;
@@ -103,6 +105,9 @@
pw.println();
pw.println(" is-headless-system-user-mode [-v | --verbose]");
pw.println(" Checks whether the device uses headless system user mode.");
+ pw.println(" is-visible-background-users-on-default-display-supported [-v | --verbose]");
+ pw.println(" Checks whether the device allows users to be start visible on background "
+ + "in the default display.");
pw.println(" It returns the effective mode, even when using emulation");
pw.println(" (to get the real mode as well, use -v or --verbose)");
pw.println();
@@ -129,6 +134,8 @@
return runSetSystemUserModeEmulation();
case "is-headless-system-user-mode":
return runIsHeadlessSystemUserMode();
+ case "is-visible-background-users-on-default-display-supported":
+ return runIsVisibleBackgroundUserOnDefaultDisplaySupported();
case "is-user-visible":
return runIsUserVisible();
default:
@@ -431,19 +438,47 @@
return -1;
}
}
-
- boolean isHsum = mService.isHeadlessSystemUserMode();
+ boolean effective = mService.isHeadlessSystemUserMode();
if (!verbose) {
// NOTE: do not change output below, as it's used by ITestDevice
// (it's ok to change the verbose option though)
- pw.println(isHsum);
+ pw.println(effective);
} else {
- pw.printf("effective=%b real=%b\n", isHsum,
+ pw.printf("effective=%b real=%b\n", effective,
RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER);
}
return 0;
}
+ private int runIsVisibleBackgroundUserOnDefaultDisplaySupported() {
+ PrintWriter pw = getOutPrintWriter();
+
+ boolean verbose = false;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-v":
+ case "--verbose":
+ verbose = true;
+ break;
+ default:
+ pw.println("Invalid option: " + opt);
+ return -1;
+ }
+ }
+
+ boolean effective = UserManager.isVisibleBackgroundUsersOnDefaultDisplayEnabled();
+ if (!verbose) {
+ // NOTE: do not change output below, as it's used by ITestDevice
+ // (it's ok to change the verbose option though)
+ pw.println(effective);
+ } else {
+ pw.printf("effective=%b real=%b\n", effective, Resources.getSystem()
+ .getBoolean(R.bool.config_multiuserVisibleBackgroundUsersOnDefaultDisplay));
+ }
+ return 0;
+ }
+
/**
* Gets the {@link UserManager} associated with the context of the given user.
*/
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 77c32ae..4cf8c09 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -121,6 +121,16 @@
.setBaseType(FLAG_PROFILE)
.setMaxAllowedPerParent(1)
.setLabel(0)
+ .setIconBadge(com.android.internal.R.drawable.ic_clone_icon_badge)
+ .setBadgePlain(com.android.internal.R.drawable.ic_clone_badge)
+ // Clone doesn't use BadgeNoBackground, so just set to BadgePlain as a placeholder.
+ .setBadgeNoBackground(com.android.internal.R.drawable.ic_clone_badge)
+ .setBadgeLabels(
+ com.android.internal.R.string.clone_profile_label_badge)
+ .setBadgeColors(
+ com.android.internal.R.color.system_neutral2_800)
+ .setDarkThemeBadgeColors(
+ com.android.internal.R.color.system_neutral2_900)
.setDefaultRestrictions(null)
.setDefaultCrossProfileIntentFilters(getDefaultCloneCrossProfileIntentFilter())
.setDefaultUserProperties(new UserProperties.Builder()
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index 1f935f90..fe8a500 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -22,6 +22,7 @@
import static android.view.Display.INVALID_DISPLAY;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND;
@@ -41,6 +42,7 @@
import android.util.EventLog;
import android.util.IndentingPrintWriter;
import android.util.IntArray;
+import android.util.Log;
import android.util.SparseIntArray;
import android.view.Display;
@@ -54,6 +56,8 @@
import com.android.server.utils.Slogf;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -76,11 +80,11 @@
*/
public final class UserVisibilityMediator implements Dumpable {
- private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
- private static final boolean VERBOSE = false; // DO NOT SUBMIT WITH TRUE
-
private static final String TAG = UserVisibilityMediator.class.getSimpleName();
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean VERBOSE = false; // DO NOT SUBMIT WITH TRUE
+
private static final String PREFIX_SECONDARY_DISPLAY_MAPPING = "SECONDARY_DISPLAY_MAPPING_";
public static final int SECONDARY_DISPLAY_MAPPING_NEEDED = 1;
public static final int SECONDARY_DISPLAY_MAPPING_NOT_NEEDED = 2;
@@ -97,7 +101,7 @@
})
public @interface SecondaryDisplayMappingStatus {}
- // TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices
+ // TODO(b/266158156): might need to change this if boot logic is refactored for HSUM devices
@VisibleForTesting
static final int INITIAL_CURRENT_USER_ID = USER_SYSTEM;
@@ -131,10 +135,23 @@
private final SparseIntArray mExtraDisplaysAssignedToUsers;
/**
- * Mapping from each started user to its profile group.
+ * Mapping of each user that started visible (key) to its profile group id (value).
+ *
+ * <p>It's used to determine not just if the user is visible, but also
+ * {@link #isProfile(int, int) if it's a profile}.
*/
@GuardedBy("mLock")
- private final SparseIntArray mStartedProfileGroupIds = new SparseIntArray();
+ private final SparseIntArray mStartedVisibleProfileGroupIds = new SparseIntArray();
+
+ /**
+ * List of profiles that have explicitly started invisible.
+ *
+ * <p>Only used for debugging purposes (and set when {@link #DBG} is {@code true}), hence we
+ * don't care about autoboxing.
+ */
+ @GuardedBy("mLock")
+ @Nullable
+ private final List<Integer> mStartedInvisibleProfileUserIds;
/**
* Handler user to call listeners
@@ -147,8 +164,7 @@
UserVisibilityMediator(Handler handler) {
this(UserManager.isVisibleBackgroundUsersEnabled(),
- // TODO(b/261538232): get visibleBackgroundUserOnDefaultDisplayAllowed from UM
- /* visibleBackgroundUserOnDefaultDisplayAllowed= */ false, handler);
+ UserManager.isVisibleBackgroundUsersOnDefaultDisplayEnabled(), handler);
}
@VisibleForTesting
@@ -164,9 +180,14 @@
mUsersAssignedToDisplayOnStart = null;
mExtraDisplaysAssignedToUsers = null;
}
+ mStartedInvisibleProfileUserIds = DBG ? new ArrayList<>(4) : null;
mHandler = handler;
- // TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices
- mStartedProfileGroupIds.put(INITIAL_CURRENT_USER_ID, INITIAL_CURRENT_USER_ID);
+ // TODO(b/266158156): might need to change this if boot logic is refactored for HSUM devices
+ mStartedVisibleProfileGroupIds.put(INITIAL_CURRENT_USER_ID, INITIAL_CURRENT_USER_ID);
+
+ if (DBG) {
+ Slogf.i(TAG, "UserVisibilityMediator created with DBG on");
+ }
}
/**
@@ -177,6 +198,8 @@
int displayId) {
Preconditions.checkArgument(!isSpecialUserId(userId), "user id cannot be generic: %d",
userId);
+ validateUserStartMode(userStartMode);
+
// This method needs to perform 4 actions:
//
// 1. Check if the user can be started given the provided arguments
@@ -207,7 +230,8 @@
Slogf.d(TAG, "result of getUserVisibilityOnStartLocked(%s)",
userAssignmentResultToString(result));
}
- if (result == USER_ASSIGNMENT_RESULT_FAILURE) {
+ if (result == USER_ASSIGNMENT_RESULT_FAILURE
+ || result == USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE) {
return result;
}
@@ -223,14 +247,29 @@
visibleUsersBefore = getVisibleUsers();
- // Set current user / profiles state
- if (userStartMode == USER_START_MODE_FOREGROUND) {
- mCurrentUserId = userId;
+ // Set current user / started users state
+ switch (userStartMode) {
+ case USER_START_MODE_FOREGROUND:
+ mCurrentUserId = userId;
+ // Fallthrough
+ case USER_START_MODE_BACKGROUND_VISIBLE:
+ if (DBG) {
+ Slogf.d(TAG, "adding visible user / profile group id mapping (%d -> %d)",
+ userId, profileGroupId);
+ }
+ mStartedVisibleProfileGroupIds.put(userId, profileGroupId);
+ break;
+ case USER_START_MODE_BACKGROUND:
+ if (mStartedInvisibleProfileUserIds != null
+ && isProfile(userId, profileGroupId)) {
+ Slogf.d(TAG, "adding user %d to list of invisible profiles", userId);
+ mStartedInvisibleProfileUserIds.add(userId);
+ }
+ break;
+ default:
+ Slogf.wtf(TAG, "invalid userStartMode passed to assignUserToDisplayOnStart: "
+ + "%d", userStartMode);
}
- if (DBG) {
- Slogf.d(TAG, "adding user / profile mapping (%d -> %d)", userId, profileGroupId);
- }
- mStartedProfileGroupIds.put(userId, profileGroupId);
// Set user / display state
switch (mappingResult) {
@@ -278,49 +317,64 @@
}
boolean visibleBackground = userStartMode == USER_START_MODE_BACKGROUND_VISIBLE;
- if (displayId == DEFAULT_DISPLAY && visibleBackground
- && !mVisibleBackgroundUserOnDefaultDisplayAllowed
- && !isProfile(userId, profileGroupId)) {
- Slogf.wtf(TAG, "cannot start full user (%d) visible on default display", userId);
- return USER_ASSIGNMENT_RESULT_FAILURE;
+ if (displayId == DEFAULT_DISPLAY && visibleBackground) {
+ if (mVisibleBackgroundUserOnDefaultDisplayAllowed && isCurrentUserLocked(userId)) {
+ // Shouldn't happen - UserController returns before calling this method
+ Slogf.wtf(TAG, "trying to start current user (%d) visible in background on default"
+ + " display", userId);
+ return USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE;
+
+ }
+ if (!mVisibleBackgroundUserOnDefaultDisplayAllowed
+ && !isProfile(userId, profileGroupId)) {
+ Slogf.wtf(TAG, "cannot start full user (%d) visible on default display", userId);
+ return USER_ASSIGNMENT_RESULT_FAILURE;
+ }
}
boolean foreground = userStartMode == USER_START_MODE_FOREGROUND;
if (displayId != DEFAULT_DISPLAY) {
if (foreground) {
- Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %b, %d) failed: cannot start "
+ Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %s, %d) failed: cannot start "
+ "foreground user on secondary display", userId, profileGroupId,
- foreground, displayId);
+ userStartModeToString(userStartMode), displayId);
return USER_ASSIGNMENT_RESULT_FAILURE;
}
if (!mVisibleBackgroundUsersEnabled) {
- Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %b, %d) failed: called on "
+ Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %s, %d) failed: called on "
+ "device that doesn't support multiple users on multiple displays",
- userId, profileGroupId, foreground, displayId);
+ userId, profileGroupId, userStartModeToString(userStartMode), displayId);
return USER_ASSIGNMENT_RESULT_FAILURE;
}
}
if (isProfile(userId, profileGroupId)) {
if (displayId != DEFAULT_DISPLAY) {
- Slogf.w(TAG, "canStartUserLocked(%d, %d, %b, %d) failed: cannot start profile user "
- + "on secondary display", userId, profileGroupId, foreground,
- displayId);
+ Slogf.w(TAG, "canStartUserLocked(%d, %d, %s, %d) failed: cannot start profile user "
+ + "on secondary display", userId, profileGroupId,
+ userStartModeToString(userStartMode), displayId);
return USER_ASSIGNMENT_RESULT_FAILURE;
}
- if (foreground) {
- Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user in "
- + "foreground", userId, profileGroupId, foreground, displayId);
- return USER_ASSIGNMENT_RESULT_FAILURE;
- } else {
- boolean isParentVisibleOnDisplay = isUserVisible(profileGroupId, displayId);
- if (DBG) {
- Slogf.d(TAG, "parent visible on display: %b", isParentVisibleOnDisplay);
- }
- return isParentVisibleOnDisplay
- ? USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE
- : USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+ switch (userStartMode) {
+ case USER_START_MODE_FOREGROUND:
+ Slogf.w(TAG, "startUser(%d, %d, %s, %d) failed: cannot start profile user in "
+ + "foreground", userId, profileGroupId,
+ userStartModeToString(userStartMode), displayId);
+ return USER_ASSIGNMENT_RESULT_FAILURE;
+ case USER_START_MODE_BACKGROUND_VISIBLE:
+ boolean isParentVisibleOnDisplay = isUserVisible(profileGroupId, displayId);
+ if (!isParentVisibleOnDisplay) {
+ Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %s, %d) failed: cannot"
+ + " start profile user visible when its parent is not visible in "
+ + "that display", userId, profileGroupId,
+ userStartModeToString(userStartMode), displayId);
+ return USER_ASSIGNMENT_RESULT_FAILURE;
+ }
+ return USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+ case USER_START_MODE_BACKGROUND:
+ return USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
}
+
}
return foreground || displayId != DEFAULT_DISPLAY
@@ -338,8 +392,9 @@
if (mVisibleBackgroundUserOnDefaultDisplayAllowed
&& userStartMode == USER_START_MODE_BACKGROUND_VISIBLE) {
int userStartedOnDefaultDisplay = getUserStartedOnDisplay(DEFAULT_DISPLAY);
- if (userStartedOnDefaultDisplay != USER_NULL) {
- Slogf.w(TAG, "getUserVisibilityOnStartLocked(): cannot start user %d visible on"
+ if (userStartedOnDefaultDisplay != USER_NULL
+ && userStartedOnDefaultDisplay != profileGroupId) {
+ Slogf.w(TAG, "canAssignUserToDisplayLocked(): cannot start user %d visible on"
+ " default display because user %d already did so", userId,
userStartedOnDefaultDisplay);
return SECONDARY_DISPLAY_MAPPING_FAILED;
@@ -360,7 +415,7 @@
// parent is the current user, as the current user is always assigned to the
// DEFAULT_DISPLAY).
if (DBG) {
- Slogf.d(TAG, "ignoring mapping for default display for user %d starting as %s",
+ Slogf.d(TAG, "Ignoring mapping for default display for user %d starting as %s",
userId, userStartModeToString(userStartMode));
}
return SECONDARY_DISPLAY_MAPPING_NOT_NEEDED;
@@ -445,7 +500,7 @@
userId, displayId);
return false;
}
- if (isStartedProfile(userId)) {
+ if (isStartedVisibleProfileLocked(userId)) {
Slogf.w(TAG, "assignUserToExtraDisplay(%d, %d): failed because user is a profile",
userId, displayId);
return false;
@@ -533,10 +588,14 @@
@GuardedBy("mLock")
private void unassignUserFromAllDisplaysOnStopLocked(@UserIdInt int userId) {
if (DBG) {
- Slogf.d(TAG, "Removing %d from mStartedProfileGroupIds (%s)", userId,
- mStartedProfileGroupIds);
+ Slogf.d(TAG, "Removing %d from mStartedVisibleProfileGroupIds (%s)", userId,
+ mStartedVisibleProfileGroupIds);
}
- mStartedProfileGroupIds.delete(userId);
+ mStartedVisibleProfileGroupIds.delete(userId);
+ if (mStartedInvisibleProfileUserIds != null) {
+ Slogf.d(TAG, "Removing %d from list of invisible profiles", userId);
+ mStartedInvisibleProfileUserIds.remove(Integer.valueOf(userId));
+ }
if (!mVisibleBackgroundUsersEnabled) {
// Don't need to update mUsersAssignedToDisplayOnStart because methods (such as
@@ -566,7 +625,8 @@
* See {@link UserManagerInternal#isUserVisible(int)}.
*/
public boolean isUserVisible(@UserIdInt int userId) {
- // First check current foreground user and their profiles (on main display)
+ // For optimization (as most devices don't support visible background users), check for
+ // current foreground user and their profiles first
if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
if (VERBOSE) {
Slogf.v(TAG, "isUserVisible(%d): true to current user or profile", userId);
@@ -575,19 +635,31 @@
}
if (!mVisibleBackgroundUsersEnabled) {
- if (DBG) {
- Slogf.d(TAG, "isUserVisible(%d): false for non-current user (or its profiles) when"
+ if (VERBOSE) {
+ Slogf.v(TAG, "isUserVisible(%d): false for non-current user (or its profiles) when"
+ " device doesn't support visible background users", userId);
}
return false;
}
- boolean visible;
+
synchronized (mLock) {
- visible = mUsersAssignedToDisplayOnStart.indexOfKey(userId) >= 0;
+ int profileGroupId;
+ synchronized (mLock) {
+ profileGroupId = mStartedVisibleProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
+ }
+ if (isProfile(userId, profileGroupId)) {
+ return isUserAssignedToDisplayOnStartLocked(profileGroupId);
+ }
+ return isUserAssignedToDisplayOnStartLocked(userId);
}
- if (DBG) {
- Slogf.d(TAG, "isUserVisible(%d): %b from mapping", userId, visible);
+ }
+
+ @GuardedBy("mLock")
+ private boolean isUserAssignedToDisplayOnStartLocked(@UserIdInt int userId) {
+ boolean visible = mUsersAssignedToDisplayOnStart.indexOfKey(userId) >= 0;
+ if (VERBOSE) {
+ Slogf.v(TAG, "isUserAssignedToDisplayOnStartLocked(%d): %b", userId, visible);
}
return visible;
}
@@ -600,7 +672,8 @@
return false;
}
- // Current user is always visible on:
+ // For optimization (as most devices don't support visible background users), check for
+ // current user and profile first. Current user is always visible on:
// - Default display
// - Secondary displays when device doesn't support visible bg users
// - Or when explicitly added (which is checked below)
@@ -622,16 +695,28 @@
}
synchronized (mLock) {
- if (mUsersAssignedToDisplayOnStart.get(userId, Display.INVALID_DISPLAY) == displayId) {
- // User assigned to display on start
- return true;
+ int profileGroupId;
+ synchronized (mLock) {
+ profileGroupId = mStartedVisibleProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
}
-
- // Check for extra display assignment
- return mExtraDisplaysAssignedToUsers.get(displayId, USER_NULL) == userId;
+ if (isProfile(userId, profileGroupId)) {
+ return isFullUserVisibleOnBackgroundLocked(profileGroupId, displayId);
+ }
+ return isFullUserVisibleOnBackgroundLocked(userId, displayId);
}
}
+ // NOTE: it doesn't check if the userId is a full user, it's up to the caller to check that
+ @GuardedBy("mLock")
+ private boolean isFullUserVisibleOnBackgroundLocked(@UserIdInt int userId, int displayId) {
+ if (mUsersAssignedToDisplayOnStart.get(userId, Display.INVALID_DISPLAY) == displayId) {
+ // User assigned to display on start
+ return true;
+ }
+ // Check for extra display assignment
+ return mExtraDisplaysAssignedToUsers.get(displayId, USER_NULL) == userId;
+ }
+
/**
* See {@link UserManagerInternal#getDisplayAssignedToUser(int)}.
*/
@@ -697,7 +782,7 @@
continue;
}
int userId = mUsersAssignedToDisplayOnStart.keyAt(i);
- if (!isStartedProfile(userId)) {
+ if (!isStartedVisibleProfileLocked(userId)) {
return userId;
} else if (DBG) {
Slogf.d(TAG, "getUserAssignedToDisplay(%d): skipping user %d because it's "
@@ -730,8 +815,8 @@
// number of users is too small, the gain is probably not worth the increase on complexity.
IntArray visibleUsers = new IntArray();
synchronized (mLock) {
- for (int i = 0; i < mStartedProfileGroupIds.size(); i++) {
- int userId = mStartedProfileGroupIds.keyAt(i);
+ for (int i = 0; i < mStartedVisibleProfileGroupIds.size(); i++) {
+ int userId = mStartedVisibleProfileGroupIds.keyAt(i);
if (isUserVisible(userId)) {
visibleUsers.add(userId);
}
@@ -764,7 +849,7 @@
}
}
- // TODO(b/242195409): remove this method if not needed anymore
+ // TODO(b/266158156): remove this method if not needed anymore
/**
* Nofify all listeners that the system user visibility changed.
*/
@@ -826,6 +911,9 @@
ipw.println("UserVisibilityMediator");
ipw.increaseIndent();
+ ipw.print("DBG: ");
+ ipw.println(DBG);
+
synchronized (mLock) {
ipw.print("Current user id: ");
ipw.println(mCurrentUserId);
@@ -833,8 +921,12 @@
ipw.print("Visible users: ");
ipw.println(getVisibleUsers());
- dumpSparseIntArray(ipw, mStartedProfileGroupIds, "started user / profile group",
- "u", "pg");
+ dumpSparseIntArray(ipw, mStartedVisibleProfileGroupIds,
+ "started visible user / profile group", "u", "pg");
+ if (mStartedInvisibleProfileUserIds != null) {
+ ipw.print("Profiles started invisible: ");
+ ipw.println(mStartedInvisibleProfileUserIds);
+ }
ipw.print("Supports visible background users on displays: ");
ipw.println(mVisibleBackgroundUsersEnabled);
@@ -924,6 +1016,15 @@
}
}
+ @GuardedBy("mLock")
+ private boolean isCurrentUserLocked(@UserIdInt int userId) {
+ // Special case as NO_PROFILE_GROUP_ID == USER_NULL
+ if (userId == USER_NULL || mCurrentUserId == USER_NULL) {
+ return false;
+ }
+ return mCurrentUserId == userId;
+ }
+
private boolean isCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) {
synchronized (mLock) {
// Special case as NO_PROFILE_GROUP_ID == USER_NULL
@@ -933,22 +1034,25 @@
if (mCurrentUserId == userId) {
return true;
}
- return mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID) == mCurrentUserId;
+ return mStartedVisibleProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID)
+ == mCurrentUserId;
}
}
- private boolean isStartedProfile(@UserIdInt int userId) {
- int profileGroupId;
- synchronized (mLock) {
- profileGroupId = mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
- }
+ @GuardedBy("mLock")
+ private boolean isStartedVisibleProfileLocked(@UserIdInt int userId) {
+ int profileGroupId = mStartedVisibleProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
return isProfile(userId, profileGroupId);
}
- private @UserIdInt int getStartedProfileGroupId(@UserIdInt int userId) {
- synchronized (mLock) {
- return mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
+ private void validateUserStartMode(@UserStartMode int userStartMode) {
+ switch (userStartMode) {
+ case USER_START_MODE_FOREGROUND:
+ case USER_START_MODE_BACKGROUND:
+ case USER_START_MODE_BACKGROUND_VISIBLE:
+ return;
}
+ throw new IllegalArgumentException("Invalid user start mode: " + userStartMode);
}
private static String secondaryDisplayMappingStatusToString(
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 287cc29..42a8cb1 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -51,7 +51,7 @@
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.permission.SplitPermissionInfoParcelable;
-import android.healthconnect.HealthConnectManager;
+import android.health.connect.HealthConnectManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
index 35c42a3..fac681a 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
@@ -943,11 +943,7 @@
return false;
}
- // System apps are never considered stopped for purposes of
- // filtering, because there may be no way for the user to
- // actually re-launch them.
- return !packageState.isSystem()
- && packageState.getUserStateOrDefault(userId).isStopped();
+ return packageState.getUserStateOrDefault(userId).isStopped();
}
}
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 7f733ef..8d7f782 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -366,12 +366,12 @@
}
mListener = listener;
}
- notifySupportedStatesChanged();
+ notifySupportedStatesChanged(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED);
notifyDeviceStateChangedIfNeeded();
}
/** Notifies the listener that the set of supported device states has changed. */
- private void notifySupportedStatesChanged() {
+ private void notifySupportedStatesChanged(@SupportedStatesUpdatedReason int reason) {
List<DeviceState> supportedStates = new ArrayList<>();
Listener listener;
synchronized (mLock) {
@@ -390,7 +390,7 @@
}
listener.onSupportedDeviceStatesChanged(
- supportedStates.toArray(new DeviceState[supportedStates.size()]));
+ supportedStates.toArray(new DeviceState[supportedStates.size()]), reason);
}
/** Computes the current device state and notifies the listener of a change, if needed. */
@@ -688,7 +688,10 @@
if (isThermalStatusCriticalOrAbove != isPreviousThermalStatusCriticalOrAbove) {
Slog.i(TAG, "Updating supported device states due to thermal status change."
+ " isThermalStatusCriticalOrAbove: " + isThermalStatusCriticalOrAbove);
- notifySupportedStatesChanged();
+ notifySupportedStatesChanged(
+ isThermalStatusCriticalOrAbove
+ ? SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL
+ : SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL);
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index db939d9..5ca3333 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -334,6 +334,8 @@
static public final String SYSTEM_DIALOG_REASON_SCREENSHOT = "screenshot";
static public final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
+ public static final String TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD = "waitForAllWindowsDrawn";
+
private static final String TALKBACK_LABEL = "TalkBack";
private static final int POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS = 800;
@@ -4902,10 +4904,15 @@
// ... eventually calls finishWindowsDrawn which will finalize our screen turn on
// as well as enabling the orientation change logic/sensor.
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER,
+ TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0);
mWindowManagerInternal.waitForAllWindowsDrawn(() -> {
if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for every display");
mHandler.sendMessage(mHandler.obtainMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE,
INVALID_DISPLAY, 0));
+
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER,
+ TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0);
}, WAITING_FOR_DRAWN_TIMEOUT, INVALID_DISPLAY);
}
@@ -4961,10 +4968,16 @@
}
} else {
mScreenOnListeners.put(displayId, screenOnListener);
+
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER,
+ TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0);
mWindowManagerInternal.waitForAllWindowsDrawn(() -> {
if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for display: " + displayId);
mHandler.sendMessage(mHandler.obtainMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE,
displayId, 0));
+
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER,
+ TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0);
}, WAITING_FOR_DRAWN_TIMEOUT, displayId);
}
}
diff --git a/services/core/java/com/android/server/security/FileIntegrityLocal.java b/services/core/java/com/android/server/security/FileIntegrityLocal.java
new file mode 100644
index 0000000..8c7219b
--- /dev/null
+++ b/services/core/java/com/android/server/security/FileIntegrityLocal.java
@@ -0,0 +1,44 @@
+/*
+ * 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.security;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.security.VerityUtils;
+
+import java.io.IOException;
+
+/**
+ * In-process API for server side FileIntegrity related infrastructure.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public final class FileIntegrityLocal {
+ private FileIntegrityLocal() {}
+
+ /**
+ * Enables fs-verity, if supported by the filesystem.
+ * @see <a href="https://www.kernel.org/doc/html/latest/filesystems/fsverity.html">
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+ public static void setUpFsVerity(@NonNull String filePath) throws IOException {
+ VerityUtils.setUpFsverity(filePath);
+ }
+}
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 6d39177..a694f5f 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -863,8 +863,9 @@
*/
private void enforceObserveSensorPrivacyPermission() {
String systemUIPackage = mContext.getString(R.string.config_systemUi);
- if (Binder.getCallingUid() == mPackageManagerInternal
- .getPackageUid(systemUIPackage, MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM)) {
+ int systemUIAppId = UserHandle.getAppId(mPackageManagerInternal
+ .getPackageUid(systemUIPackage, MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM));
+ if (UserHandle.getCallingAppId() == systemUIAppId) {
// b/221782106, possible race condition with role grant might bootloop device.
return;
}
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index 111b4f6..5fd70a8 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -243,6 +243,8 @@
}
builder.setConfigureAutoDetectionEnabledCapability(configureAutoDetectionEnabledCapability);
+ builder.setUseLocationEnabled(mLocationEnabledSetting);
+
boolean deviceHasLocationTimeZoneDetection = isGeoDetectionSupported();
boolean deviceHasTelephonyDetection = isTelephonyDetectionSupported();
diff --git a/services/core/java/com/android/server/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java
new file mode 100644
index 0000000..53f04d7
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/HalVibration.java
@@ -0,0 +1,219 @@
+/*
+ * 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.vibrator;
+
+import android.annotation.Nullable;
+import android.os.CombinedVibration;
+import android.os.IBinder;
+import android.os.VibrationAttributes;
+import android.os.VibrationEffect;
+import android.util.SparseArray;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Function;
+
+/**
+ * Represents a vibration defined by a {@link CombinedVibration} that will be performed by
+ * the IVibrator HAL.
+ */
+final class HalVibration extends Vibration {
+
+ public final VibrationAttributes attrs;
+ public final long id;
+ public final int uid;
+ public final int displayId;
+ public final String opPkg;
+ public final String reason;
+ public final IBinder token;
+ public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>();
+
+ /** The actual effect to be played. */
+ @Nullable
+ private CombinedVibration mEffect;
+
+ /**
+ * The original effect that was requested. Typically these two things differ because the effect
+ * was scaled based on the users vibration intensity settings.
+ */
+ @Nullable
+ private CombinedVibration mOriginalEffect;
+
+ /** Vibration status. */
+ private Vibration.Status mStatus;
+
+ /** Vibration runtime stats. */
+ private final VibrationStats mStats = new VibrationStats();
+
+ /** A {@link CountDownLatch} to enable waiting for completion. */
+ private final CountDownLatch mCompletionLatch = new CountDownLatch(1);
+
+ HalVibration(IBinder token, int id, CombinedVibration effect,
+ VibrationAttributes attrs, int uid, int displayId, String opPkg, String reason) {
+ this.token = token;
+ this.mEffect = effect;
+ this.id = id;
+ this.attrs = attrs;
+ this.uid = uid;
+ this.displayId = displayId;
+ this.opPkg = opPkg;
+ this.reason = reason;
+ mStatus = Vibration.Status.RUNNING;
+ }
+
+ VibrationStats stats() {
+ return mStats;
+ }
+
+ /**
+ * Set the {@link Status} of this vibration and reports the current system time as this
+ * vibration end time, for debugging purposes.
+ *
+ * <p>This method will only accept given value if the current status is {@link
+ * Status#RUNNING}.
+ */
+ public void end(EndInfo info) {
+ if (hasEnded()) {
+ // Vibration already ended, keep first ending status set and ignore this one.
+ return;
+ }
+ mStatus = info.status;
+ mStats.reportEnded(info.endedByUid, info.endedByUsage);
+ mCompletionLatch.countDown();
+ }
+
+ /** Waits indefinitely until another thread calls {@link #end} on this vibration. */
+ public void waitForEnd() throws InterruptedException {
+ mCompletionLatch.await();
+ }
+
+ /**
+ * Return the effect to be played when given prebaked effect id is not supported by the
+ * vibrator.
+ */
+ @Nullable
+ public VibrationEffect getFallback(int effectId) {
+ return mFallbacks.get(effectId);
+ }
+
+ /**
+ * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported,
+ * which might be necessary for replacement in realtime.
+ */
+ public void addFallback(int effectId, VibrationEffect effect) {
+ mFallbacks.put(effectId, effect);
+ }
+
+ /**
+ * Applied update function to the current effect held by this vibration, and to each fallback
+ * effect added.
+ */
+ public void updateEffects(Function<VibrationEffect, VibrationEffect> updateFn) {
+ CombinedVibration newEffect = transformCombinedEffect(mEffect, updateFn);
+ if (!newEffect.equals(mEffect)) {
+ if (mOriginalEffect == null) {
+ mOriginalEffect = mEffect;
+ }
+ mEffect = newEffect;
+ }
+ for (int i = 0; i < mFallbacks.size(); i++) {
+ mFallbacks.setValueAt(i, updateFn.apply(mFallbacks.valueAt(i)));
+ }
+ }
+
+ /**
+ * Creates a new {@link CombinedVibration} by applying the given transformation function
+ * to each {@link VibrationEffect}.
+ */
+ private static CombinedVibration transformCombinedEffect(
+ CombinedVibration combinedEffect, Function<VibrationEffect, VibrationEffect> fn) {
+ if (combinedEffect instanceof CombinedVibration.Mono) {
+ VibrationEffect effect = ((CombinedVibration.Mono) combinedEffect).getEffect();
+ return CombinedVibration.createParallel(fn.apply(effect));
+ } else if (combinedEffect instanceof CombinedVibration.Stereo) {
+ SparseArray<VibrationEffect> effects =
+ ((CombinedVibration.Stereo) combinedEffect).getEffects();
+ CombinedVibration.ParallelCombination combination =
+ CombinedVibration.startParallel();
+ for (int i = 0; i < effects.size(); i++) {
+ combination.addVibrator(effects.keyAt(i), fn.apply(effects.valueAt(i)));
+ }
+ return combination.combine();
+ } else if (combinedEffect instanceof CombinedVibration.Sequential) {
+ List<CombinedVibration> effects =
+ ((CombinedVibration.Sequential) combinedEffect).getEffects();
+ CombinedVibration.SequentialCombination combination =
+ CombinedVibration.startSequential();
+ for (CombinedVibration effect : effects) {
+ combination.addNext(transformCombinedEffect(effect, fn));
+ }
+ return combination.combine();
+ } else {
+ // Unknown combination, return same effect.
+ return combinedEffect;
+ }
+ }
+
+ /** Return true is current status is different from {@link Status#RUNNING}. */
+ public boolean hasEnded() {
+ return mStatus != Status.RUNNING;
+ }
+
+ /** Return true is effect is a repeating vibration. */
+ public boolean isRepeating() {
+ return mEffect.getDuration() == Long.MAX_VALUE;
+ }
+
+ /** Return the effect that should be played by this vibration. */
+ @Nullable
+ public CombinedVibration getEffect() {
+ return mEffect;
+ }
+
+ /**
+ * Return {@link Vibration.DebugInfo} with read-only debug information about this vibration.
+ */
+ public Vibration.DebugInfo getDebugInfo() {
+ return new Vibration.DebugInfo(mStatus, mStats, mEffect, mOriginalEffect, /* scale= */ 0,
+ attrs, uid, displayId, opPkg, reason);
+ }
+
+ /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
+ public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
+ int vibrationType = isRepeating()
+ ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
+ : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
+ return new VibrationStats.StatsInfo(
+ uid, vibrationType, attrs.getUsage(), mStatus, mStats, completionUptimeMillis);
+ }
+
+ /**
+ * Returns true if this vibration can pipeline with the specified one.
+ *
+ * <p>Note that currently, repeating vibrations can't pipeline with following vibrations,
+ * because the cancel() call to stop the repetition will cancel a pending vibration too. This
+ * can be changed if we have a use-case to reason around behavior for. It may also be nice to
+ * pipeline very short vibrations together, regardless of the flag.
+ */
+ public boolean canPipelineWith(HalVibration vib) {
+ return uid == vib.uid && attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
+ && vib.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
+ && !isRepeating();
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/Step.java b/services/core/java/com/android/server/vibrator/Step.java
index 042e8a0..5faa516 100644
--- a/services/core/java/com/android/server/vibrator/Step.java
+++ b/services/core/java/com/android/server/vibrator/Step.java
@@ -38,7 +38,7 @@
this.startTime = startTime;
}
- protected Vibration getVibration() {
+ protected HalVibration getVibration() {
return conductor.getVibration();
}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index a451ee2..5667c72 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.CombinedVibration;
-import android.os.IBinder;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.vibrator.PrebakedSegment;
@@ -27,20 +26,16 @@
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
-import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
-import com.android.internal.util.FrameworkStatsLog;
-
import java.text.SimpleDateFormat;
import java.util.Date;
-import java.util.List;
import java.util.Objects;
-import java.util.concurrent.CountDownLatch;
-import java.util.function.Function;
-/** Represents a vibration request to the vibrator service. */
-final class Vibration {
+/**
+ * The base class for all vibrations.
+ */
+class Vibration {
private static final SimpleDateFormat DEBUG_DATE_FORMAT =
new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
@@ -85,186 +80,6 @@
}
}
- public final VibrationAttributes attrs;
- public final long id;
- public final int uid;
- public final int displayId;
- public final String opPkg;
- public final String reason;
- public final IBinder token;
- public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>();
-
- /** The actual effect to be played. */
- @Nullable
- private CombinedVibration mEffect;
-
- /**
- * The original effect that was requested. Typically these two things differ because the effect
- * was scaled based on the users vibration intensity settings.
- */
- @Nullable
- private CombinedVibration mOriginalEffect;
-
- /** Vibration status. */
- private Vibration.Status mStatus;
-
- /** Vibration runtime stats. */
- private final VibrationStats mStats = new VibrationStats();
-
- /** A {@link CountDownLatch} to enable waiting for completion. */
- private final CountDownLatch mCompletionLatch = new CountDownLatch(1);
-
- Vibration(IBinder token, int id, CombinedVibration effect,
- VibrationAttributes attrs, int uid, int displayId, String opPkg, String reason) {
- this.token = token;
- this.mEffect = effect;
- this.id = id;
- this.attrs = attrs;
- this.uid = uid;
- this.displayId = displayId;
- this.opPkg = opPkg;
- this.reason = reason;
- mStatus = Vibration.Status.RUNNING;
- }
-
- VibrationStats stats() {
- return mStats;
- }
-
- /**
- * Set the {@link Status} of this vibration and reports the current system time as this
- * vibration end time, for debugging purposes.
- *
- * <p>This method will only accept given value if the current status is {@link
- * Status#RUNNING}.
- */
- public void end(EndInfo info) {
- if (hasEnded()) {
- // Vibration already ended, keep first ending status set and ignore this one.
- return;
- }
- mStatus = info.status;
- mStats.reportEnded(info.endedByUid, info.endedByUsage);
- mCompletionLatch.countDown();
- }
-
- /** Waits indefinitely until another thread calls {@link #end} on this vibration. */
- public void waitForEnd() throws InterruptedException {
- mCompletionLatch.await();
- }
-
- /**
- * Return the effect to be played when given prebaked effect id is not supported by the
- * vibrator.
- */
- @Nullable
- public VibrationEffect getFallback(int effectId) {
- return mFallbacks.get(effectId);
- }
-
- /**
- * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported,
- * which might be necessary for replacement in realtime.
- */
- public void addFallback(int effectId, VibrationEffect effect) {
- mFallbacks.put(effectId, effect);
- }
-
- /**
- * Applied update function to the current effect held by this vibration, and to each fallback
- * effect added.
- */
- public void updateEffects(Function<VibrationEffect, VibrationEffect> updateFn) {
- CombinedVibration newEffect = transformCombinedEffect(mEffect, updateFn);
- if (!newEffect.equals(mEffect)) {
- if (mOriginalEffect == null) {
- mOriginalEffect = mEffect;
- }
- mEffect = newEffect;
- }
- for (int i = 0; i < mFallbacks.size(); i++) {
- mFallbacks.setValueAt(i, updateFn.apply(mFallbacks.valueAt(i)));
- }
- }
-
- /**
- * Creates a new {@link CombinedVibration} by applying the given transformation function
- * to each {@link VibrationEffect}.
- */
- private static CombinedVibration transformCombinedEffect(
- CombinedVibration combinedEffect, Function<VibrationEffect, VibrationEffect> fn) {
- if (combinedEffect instanceof CombinedVibration.Mono) {
- VibrationEffect effect = ((CombinedVibration.Mono) combinedEffect).getEffect();
- return CombinedVibration.createParallel(fn.apply(effect));
- } else if (combinedEffect instanceof CombinedVibration.Stereo) {
- SparseArray<VibrationEffect> effects =
- ((CombinedVibration.Stereo) combinedEffect).getEffects();
- CombinedVibration.ParallelCombination combination =
- CombinedVibration.startParallel();
- for (int i = 0; i < effects.size(); i++) {
- combination.addVibrator(effects.keyAt(i), fn.apply(effects.valueAt(i)));
- }
- return combination.combine();
- } else if (combinedEffect instanceof CombinedVibration.Sequential) {
- List<CombinedVibration> effects =
- ((CombinedVibration.Sequential) combinedEffect).getEffects();
- CombinedVibration.SequentialCombination combination =
- CombinedVibration.startSequential();
- for (CombinedVibration effect : effects) {
- combination.addNext(transformCombinedEffect(effect, fn));
- }
- return combination.combine();
- } else {
- // Unknown combination, return same effect.
- return combinedEffect;
- }
- }
-
- /** Return true is current status is different from {@link Status#RUNNING}. */
- public boolean hasEnded() {
- return mStatus != Status.RUNNING;
- }
-
- /** Return true is effect is a repeating vibration. */
- public boolean isRepeating() {
- return mEffect.getDuration() == Long.MAX_VALUE;
- }
-
- /** Return the effect that should be played by this vibration. */
- @Nullable
- public CombinedVibration getEffect() {
- return mEffect;
- }
-
- /** Return {@link Vibration.DebugInfo} with read-only debug information about this vibration. */
- public Vibration.DebugInfo getDebugInfo() {
- return new Vibration.DebugInfo(mStatus, mStats, mEffect, mOriginalEffect, /* scale= */ 0,
- attrs, uid, displayId, opPkg, reason);
- }
-
- /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
- public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
- int vibrationType = isRepeating()
- ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
- : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
- return new VibrationStats.StatsInfo(
- uid, vibrationType, attrs.getUsage(), mStatus, mStats, completionUptimeMillis);
- }
-
- /**
- * Returns true if this vibration can pipeline with the specified one.
- *
- * <p>Note that currently, repeating vibrations can't pipeline with following vibrations,
- * because the cancel() call to stop the repetition will cancel a pending vibration too. This
- * can be changed if we have a use-case to reason around behavior for. It may also be nice to
- * pipeline very short vibrations together, regardless of the flag.
- */
- public boolean canPipelineWith(Vibration vib) {
- return uid == vib.uid && attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
- && vib.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
- && !isRepeating();
- }
-
/** Immutable info passed as a signal to end a vibration. */
static final class EndInfo {
/** The {@link Status} to be set to the vibration when it ends with this info. */
@@ -505,5 +320,4 @@
proto.end(token);
}
}
-
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 141be70..11cab4b 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -68,7 +68,7 @@
// Not guarded by lock because they're not modified by this conductor, it's used here only to
// check immutable attributes. The status and other mutable states are changed by the service or
// by the vibrator steps.
- private final Vibration mVibration;
+ private final HalVibration mVibration;
private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>();
@@ -95,7 +95,7 @@
private int mRemainingStartSequentialEffectSteps;
private int mSuccessfulVibratorOnSteps;
- VibrationStepConductor(Vibration vib, VibrationSettings vibrationSettings,
+ VibrationStepConductor(HalVibration vib, VibrationSettings vibrationSettings,
DeviceVibrationEffectAdapter effectAdapter,
SparseArray<VibratorController> availableVibrators,
VibrationThread.VibratorManagerHooks vibratorManagerHooks) {
@@ -160,7 +160,7 @@
mVibration.stats().reportStarted();
}
- public Vibration getVibration() {
+ public HalVibration getVibration() {
// No thread assertion: immutable
return mVibration;
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index 3fd9594..d2dc43c 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -33,12 +33,12 @@
import java.util.NoSuchElementException;
import java.util.Objects;
-/** Plays a {@link Vibration} in dedicated thread. */
+/** Plays a {@link HalVibration} in dedicated thread. */
final class VibrationThread extends Thread {
static final String TAG = "VibrationThread";
static final boolean DEBUG = false;
- /** Calls into VibratorManager functionality needed for playing a {@link Vibration}. */
+ /** Calls into VibratorManager functionality needed for playing a {@link HalVibration}. */
interface VibratorManagerHooks {
/**
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 966329e..c87332d 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -385,12 +385,13 @@
}
/**
- * An internal-only version of vibrate that allows the caller access to the {@link Vibration}.
+ * An internal-only version of vibrate that allows the caller access to the
+ * {@link HalVibration}.
* The Vibration is only returned if it is ongoing after this method returns.
*/
@VisibleForTesting
@Nullable
- Vibration vibrateInternal(int uid, int displayId, String opPkg,
+ HalVibration vibrateInternal(int uid, int displayId, String opPkg,
@NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs,
String reason, IBinder token) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
@@ -407,8 +408,8 @@
}
attrs = fixupVibrationAttributes(attrs, effect);
// Create Vibration.Stats as close to the received request as possible, for tracking.
- Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs,
- uid, displayId, opPkg, reason);
+ HalVibration vib = new HalVibration(token, mNextVibrationId.getAndIncrement(), effect,
+ attrs, uid, displayId, opPkg, reason);
fillVibrationFallbacks(vib, effect);
if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
@@ -639,7 +640,7 @@
return;
}
- Vibration vib = mCurrentVibration.getVibration();
+ HalVibration vib = mCurrentVibration.getVibration();
Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
vib.uid, vib.displayId, vib.opPkg, vib.attrs);
@@ -683,7 +684,7 @@
}
@GuardedBy("mLock")
- private Vibration.Status startVibrationLocked(Vibration vib) {
+ private Vibration.Status startVibrationLocked(HalVibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
try {
vib.updateEffects(effect -> mVibrationScaler.scale(effect, vib.attrs.getUsage()));
@@ -716,7 +717,7 @@
private Vibration.Status startVibrationOnThreadLocked(VibrationStepConductor conductor) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationThreadLocked");
try {
- Vibration vib = conductor.getVibration();
+ HalVibration vib = conductor.getVibration();
int mode = startAppOpModeLocked(vib.uid, vib.opPkg, vib.attrs);
switch (mode) {
case AppOpsManager.MODE_ALLOWED:
@@ -741,7 +742,7 @@
}
@GuardedBy("mLock")
- private void endVibrationLocked(Vibration vib, Vibration.EndInfo vibrationEndInfo,
+ private void endVibrationLocked(HalVibration vib, Vibration.EndInfo vibrationEndInfo,
boolean shouldWriteStats) {
vib.end(vibrationEndInfo);
logVibrationStatus(vib.uid, vib.attrs, vibrationEndInfo.status);
@@ -763,7 +764,8 @@
vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
}
- private void logVibrationStatus(int uid, VibrationAttributes attrs, Vibration.Status status) {
+ private void logVibrationStatus(int uid, VibrationAttributes attrs,
+ Vibration.Status status) {
switch (status) {
case IGNORED_BACKGROUND:
Slog.e(TAG, "Ignoring incoming vibration as process with"
@@ -813,7 +815,7 @@
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
try {
- Vibration vib = mCurrentVibration.getVibration();
+ HalVibration vib = mCurrentVibration.getVibration();
if (DEBUG) {
Slog.d(TAG, "Reporting vibration " + vib.id + " finished with " + vibrationEndInfo);
}
@@ -857,13 +859,13 @@
*/
@GuardedBy("mLock")
@Nullable
- private Vibration.Status shouldIgnoreVibrationForOngoingLocked(Vibration vib) {
+ private Vibration.Status shouldIgnoreVibrationForOngoingLocked(HalVibration vib) {
if (mCurrentVibration == null || vib.isRepeating()) {
// Incoming repeating vibrations always take precedence over ongoing vibrations.
return null;
}
- Vibration currentVibration = mCurrentVibration.getVibration();
+ HalVibration currentVibration = mCurrentVibration.getVibration();
if (currentVibration.hasEnded() || mCurrentVibration.wasNotifiedToCancel()) {
// Current vibration has ended or is cancelling, should not block incoming vibrations.
return null;
@@ -945,7 +947,7 @@
* @param token The binder token to identify the vibration origin. Only vibrations
* started with the same token can be cancelled with it.
*/
- private boolean shouldCancelVibration(Vibration vib, int usageFilter, IBinder token) {
+ private boolean shouldCancelVibration(HalVibration vib, int usageFilter, IBinder token) {
return (vib.token == token) && shouldCancelVibration(vib.attrs, usageFilter);
}
@@ -1041,7 +1043,7 @@
* Sets fallback effects to all prebaked ones in given combination of effects, based on {@link
* VibrationSettings#getFallbackEffect}.
*/
- private void fillVibrationFallbacks(Vibration vib, CombinedVibration effect) {
+ private void fillVibrationFallbacks(HalVibration vib, CombinedVibration effect) {
if (effect instanceof CombinedVibration.Mono) {
fillVibrationFallbacks(vib, ((CombinedVibration.Mono) effect).getEffect());
} else if (effect instanceof CombinedVibration.Stereo) {
@@ -1059,7 +1061,7 @@
}
}
- private void fillVibrationFallbacks(Vibration vib, VibrationEffect effect) {
+ private void fillVibrationFallbacks(HalVibration vib, VibrationEffect effect) {
VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
int segmentCount = composed.getSegments().size();
for (int i = 0; i < segmentCount; i++) {
@@ -1183,7 +1185,7 @@
if (conductor == null) {
return false;
}
- Vibration vib = conductor.getVibration();
+ HalVibration vib = conductor.getVibration();
return mVibrationSettings.shouldCancelVibrationOnScreenOff(
vib.uid, vib.opPkg, vib.attrs.getUsage(), vib.stats().getCreateUptimeMillis());
}
@@ -1535,7 +1537,7 @@
mPreviousVibrationsLimit = limit;
}
- synchronized void record(Vibration vib) {
+ synchronized void record(HalVibration vib) {
int usage = vib.attrs.getUsage();
if (!mPreviousVibrations.contains(usage)) {
mPreviousVibrations.put(usage, new LinkedList<>());
@@ -1870,7 +1872,7 @@
// only cancel background vibrations.
IBinder deathBinder = commonOptions.background ? VibratorManagerService.this
: mShellCallbacksToken;
- Vibration vib = vibrateInternal(Binder.getCallingUid(), Display.DEFAULT_DISPLAY,
+ HalVibration vib = vibrateInternal(Binder.getCallingUid(), Display.DEFAULT_DISPLAY,
SHELL_PACKAGE_NAME, combined, attrs, commonOptions.description, deathBinder);
if (vib != null && !commonOptions.background) {
try {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 262b964..5fd57e3 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -17,7 +17,6 @@
package com.android.server.wallpaper;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-import static android.Manifest.permission.QUERY_ALL_PACKAGES;
import static android.Manifest.permission.READ_WALLPAPER_INTERNAL;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.WallpaperManager.COMMAND_REAPPLY;
@@ -67,6 +66,7 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
@@ -759,6 +759,7 @@
private final Context mContext;
private final WindowManagerInternal mWindowManagerInternal;
+ private final PackageManagerInternal mPackageManagerInternal;
private final IPackageManager mIPackageManager;
private final ActivityManager mActivityManager;
private final MyPackageMonitor mMonitor;
@@ -1604,6 +1605,7 @@
context.getResources().getString(R.string.image_wallpaper_component));
mDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(context);
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mIPackageManager = AppGlobals.getPackageManager();
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
DisplayManager dm = mContext.getSystemService(DisplayManager.class);
@@ -2283,20 +2285,23 @@
@Override
public WallpaperInfo getWallpaperInfoWithFlags(@SetWallpaperFlags int which, int userId) {
- final boolean allow =
- hasPermission(READ_WALLPAPER_INTERNAL) || hasPermission(QUERY_ALL_PACKAGES);
- if (allow) {
- userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
- Binder.getCallingUid(), userId, false, true, "getWallpaperInfo", null);
- synchronized (mLock) {
- WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
- : mWallpaperMap.get(userId);
- if (wallpaper != null && wallpaper.connection != null) {
- return wallpaper.connection.mInfo;
- }
+
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false, true, "getWallpaperInfo", null);
+ synchronized (mLock) {
+ WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
+ : mWallpaperMap.get(userId);
+ if (wallpaper == null
+ || wallpaper.connection == null
+ || wallpaper.connection.mInfo == null) return null;
+
+ WallpaperInfo info = wallpaper.connection.mInfo;
+ if (hasPermission(READ_WALLPAPER_INTERNAL)
+ || mPackageManagerInternal.canQueryPackage(
+ Binder.getCallingUid(), info.getComponent().getPackageName())) {
+ return info;
}
}
-
return null;
}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 316b12a..4507637 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -749,6 +749,8 @@
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null) {
+ EventLogTags.writeWmSetRequestedOrientation(requestedOrientation,
+ r.shortComponentName);
r.setRequestedOrientation(requestedOrientation);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0c97d7d..c289153 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -10333,8 +10333,8 @@
// TODO(b/263592337): Explore enabling compat fake focus for fullscreen, e.g. for when
// covered with bubbles.
boolean shouldSendCompatFakeFocus() {
- return mWmService.mLetterboxConfiguration.isCompatFakeFocusEnabled(info)
- && inMultiWindowMode() && !inPinnedWindowingMode() && !inFreeformWindowingMode();
+ return mLetterboxUiController.shouldSendFakeFocus() && inMultiWindowMode()
+ && !inPinnedWindowingMode() && !inFreeformWindowingMode();
}
static class Builder {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f183760..4174bfe 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -46,7 +46,7 @@
import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
import static android.view.Display.STATE_UNKNOWN;
import static android.view.Display.isSuspendedState;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
import static android.view.Surface.ROTATION_0;
@@ -120,12 +120,10 @@
import static com.android.server.wm.DisplayContentProto.FOCUSED_APP;
import static com.android.server.wm.DisplayContentProto.FOCUSED_ROOT_TASK_ID;
import static com.android.server.wm.DisplayContentProto.ID;
-import static com.android.server.wm.DisplayContentProto.IME_INSETS_SOURCE_PROVIDER;
import static com.android.server.wm.DisplayContentProto.IME_POLICY;
import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_CONTROL_TARGET;
import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_INPUT_TARGET;
import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_TARGET;
-import static com.android.server.wm.DisplayContentProto.INSETS_SOURCE_PROVIDERS;
import static com.android.server.wm.DisplayContentProto.IS_SLEEPING;
import static com.android.server.wm.DisplayContentProto.KEEP_CLEAR_AREAS;
import static com.android.server.wm.DisplayContentProto.MIN_SIZE_OF_RESIZEABLE_TASK_DP;
@@ -293,6 +291,13 @@
@Retention(RetentionPolicy.SOURCE)
@interface ForceScalingMode {}
+ private static final InsetsState.OnTraverseCallbacks COPY_SOURCE_VISIBILITY =
+ new InsetsState.OnTraverseCallbacks() {
+ public void onIdMatch(InsetsSource source1, InsetsSource source2) {
+ source1.setVisible(source2.isVisible());
+ }
+ };
+
final ActivityTaskManagerService mAtmService;
/**
@@ -1756,12 +1761,9 @@
return ROTATION_UNDEFINED;
}
if (activityOrientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
- // TODO(b/266280737): Use ActivityRecord#canDefineOrientationForActivitiesAbove
final ActivityRecord nextCandidate = getActivity(
- a -> a.getOverrideOrientation() != SCREEN_ORIENTATION_UNSET
- && a.getOverrideOrientation()
- != ActivityInfo.SCREEN_ORIENTATION_BEHIND,
- r, false /* includeBoundary */, true /* traverseTopToBottom */);
+ a -> a.canDefineOrientationForActivitiesAbove() /* callback */,
+ r /* boundary */, false /* includeBoundary */, true /* traverseTopToBottom */);
if (nextCandidate != null) {
r = nextCandidate;
}
@@ -2091,12 +2093,7 @@
mFixedRotationLaunchingApp.getFixedRotationTransformInsetsState();
if (rotatedState != null) {
final InsetsState state = mInsetsStateController.getRawInsetsState();
- for (int i = 0; i < InsetsState.SIZE; i++) {
- final InsetsSource source = state.peekSource(i);
- if (source != null) {
- rotatedState.setSourceVisible(i, source.isVisible());
- }
- }
+ InsetsState.traverse(rotatedState, state, COPY_SOURCE_VISIBILITY);
}
}
forAllWindows(dispatchInsetsChanged, true /* traverseTopToBottom */);
@@ -3380,7 +3377,7 @@
int getInputMethodWindowVisibleHeight() {
final InsetsState state = getInsetsStateController().getRawInsetsState();
- final InsetsSource imeSource = state.peekSource(ITYPE_IME);
+ final InsetsSource imeSource = state.peekSource(ID_IME);
if (imeSource == null || !imeSource.isVisible()) {
return 0;
}
@@ -3547,14 +3544,7 @@
mCurrentFocus.dumpDebug(proto, CURRENT_FOCUS, logLevel);
}
if (mInsetsStateController != null) {
- for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
- final WindowContainerInsetsSourceProvider provider = mInsetsStateController
- .peekSourceProvider(type);
- if (provider != null) {
- provider.dumpDebug(proto, type == ITYPE_IME ? IME_INSETS_SOURCE_PROVIDER :
- INSETS_SOURCE_PROVIDERS, logLevel);
- }
- }
+ mInsetsStateController.dumpDebug(proto, logLevel);
}
proto.write(IME_POLICY, getImePolicy());
for (Rect r : getKeepClearAreas()) {
@@ -4041,7 +4031,7 @@
final int imePid = mInputMethodWindow.mSession.mPid;
mAtmService.onImeWindowSetOnDisplayArea(imePid, mImeWindowsContainer);
}
- mInsetsStateController.getSourceProvider(ITYPE_IME).setWindowContainer(win,
+ mInsetsStateController.getSourceProvider(ID_IME).setWindowContainer(win,
mDisplayPolicy.getImeSourceFrameProvider(), null);
computeImeTarget(true /* updateImeTarget */);
updateImeControlTarget();
@@ -4290,7 +4280,8 @@
assignWindowLayers(true /* setLayoutNeeded */);
// 3. The z-order of IME might have been changed. Update the above insets state.
mInsetsStateController.updateAboveInsetsState(
- mInsetsStateController.getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME));
+ mInsetsStateController.getRawInsetsState().isSourceOrDefaultVisible(
+ ID_IME, ime()));
// 4. Update the IME control target to apply any inset change and animation.
// 5. Reparent the IME container surface to either the input target app, or the IME window
// parent.
@@ -4516,7 +4507,7 @@
ProtoLog.i(WM_DEBUG_IME, "setInputMethodInputTarget %s", target);
setImeInputTarget(target);
mInsetsStateController.updateAboveInsetsState(mInsetsStateController
- .getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME));
+ .getRawInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
// Force updating the IME parent when the IME control target has been updated to the
// remote target but updateImeParent not happen because ImeLayeringTarget and
// ImeInputTarget are different. Then later updateImeParent would be ignored when there
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index e984456..7f785af 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -16,10 +16,8 @@
package com.android.server.wm;
-import static android.view.InsetsState.ITYPE_BOTTOM_DISPLAY_CUTOUT;
-import static android.view.InsetsState.ITYPE_LEFT_DISPLAY_CUTOUT;
-import static android.view.InsetsState.ITYPE_RIGHT_DISPLAY_CUTOUT;
-import static android.view.InsetsState.ITYPE_TOP_DISPLAY_CUTOUT;
+import static android.view.InsetsSource.createId;
+import static android.view.WindowInsets.Type.displayCutout;
import android.annotation.NonNull;
import android.graphics.Rect;
@@ -38,6 +36,12 @@
* @hide
*/
public class DisplayFrames {
+
+ private static final int ID_DISPLAY_CUTOUT_LEFT = createId(null, 0, displayCutout());
+ private static final int ID_DISPLAY_CUTOUT_TOP = createId(null, 1, displayCutout());
+ private static final int ID_DISPLAY_CUTOUT_RIGHT = createId(null, 2, displayCutout());
+ private static final int ID_DISPLAY_CUTOUT_BOTTOM = createId(null, 3, displayCutout());
+
public final InsetsState mInsetsState;
/**
@@ -97,28 +101,28 @@
state.setDisplayShape(displayShape);
state.getDisplayCutoutSafe(safe);
if (safe.left > unrestricted.left) {
- state.getSource(ITYPE_LEFT_DISPLAY_CUTOUT).setFrame(
+ state.getOrCreateSource(ID_DISPLAY_CUTOUT_LEFT, displayCutout()).setFrame(
unrestricted.left, unrestricted.top, safe.left, unrestricted.bottom);
} else {
- state.removeSource(ITYPE_LEFT_DISPLAY_CUTOUT);
+ state.removeSource(ID_DISPLAY_CUTOUT_LEFT);
}
if (safe.top > unrestricted.top) {
- state.getSource(ITYPE_TOP_DISPLAY_CUTOUT).setFrame(
+ state.getOrCreateSource(ID_DISPLAY_CUTOUT_TOP, displayCutout()).setFrame(
unrestricted.left, unrestricted.top, unrestricted.right, safe.top);
} else {
- state.removeSource(ITYPE_TOP_DISPLAY_CUTOUT);
+ state.removeSource(ID_DISPLAY_CUTOUT_TOP);
}
if (safe.right < unrestricted.right) {
- state.getSource(ITYPE_RIGHT_DISPLAY_CUTOUT).setFrame(
+ state.getOrCreateSource(ID_DISPLAY_CUTOUT_RIGHT, displayCutout()).setFrame(
safe.right, unrestricted.top, unrestricted.right, unrestricted.bottom);
} else {
- state.removeSource(ITYPE_RIGHT_DISPLAY_CUTOUT);
+ state.removeSource(ID_DISPLAY_CUTOUT_RIGHT);
}
if (safe.bottom < unrestricted.bottom) {
- state.getSource(ITYPE_BOTTOM_DISPLAY_CUTOUT).setFrame(
+ state.getOrCreateSource(ID_DISPLAY_CUTOUT_BOTTOM, displayCutout()).setFrame(
unrestricted.left, safe.bottom, unrestricted.right, unrestricted.bottom);
} else {
- state.removeSource(ITYPE_BOTTOM_DISPLAY_CUTOUT);
+ state.removeSource(ID_DISPLAY_CUTOUT_BOTTOM);
}
return true;
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index d759ff5..e87680a 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1741,21 +1741,6 @@
}
/**
- * Get the Navigation Bar Frame height. This dimension is the height of the navigation bar that
- * is used for spacing to show additional buttons on the navigation bar (such as the ime
- * switcher when ime is visible).
- *
- * @param rotation specifies rotation to return dimension from
- * @return navigation bar frame height
- */
- private int getNavigationBarFrameHeight(int rotation) {
- if (mNavigationBar == null) {
- return 0;
- }
- return mNavigationBar.mAttrs.forRotation(rotation).height;
- }
-
- /**
* Return corner radius in pixels that should be used on windows in order to cover the display.
*
* The radius is only valid for internal displays, since the corner radius of external displays
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 3ffb2fa..e04900c 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -16,6 +16,8 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
@@ -34,6 +36,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StringRes;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.RefreshCallbackItem;
import android.app.servertransaction.ResumeActivityItem;
@@ -44,10 +47,13 @@
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.widget.Toast;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.UiThread;
import java.util.Map;
import java.util.Set;
@@ -232,6 +238,27 @@
activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
}
+ /**
+ * Notifies that animation in {@link ScreenAnimationRotation} has finished.
+ *
+ * <p>This class uses this signal as a trigger for notifying the user about forced rotation
+ * reason with the {@link Toast}.
+ */
+ void onScreenRotationAnimationFinished() {
+ if (!isTreatmentEnabledForDisplay() || mCameraIdPackageBiMap.isEmpty()) {
+ return;
+ }
+ ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+ /* considerKeyguardState= */ true);
+ if (topActivity == null
+ // Checking windowing mode on activity level because we don't want to
+ // show toast in case of activity embedding.
+ || topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+ return;
+ }
+ showToast(R.string.display_rotation_camera_compat_toast_after_rotation);
+ }
+
String getSummaryForDisplayRotationHistoryRecord() {
String summaryIfEnabled = "";
if (isTreatmentEnabledForDisplay()) {
@@ -281,6 +308,10 @@
&& mDisplayContent.getDisplay().getType() == TYPE_INTERNAL;
}
+ boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
+ return isTreatmentEnabledForDisplay() && isCameraActiveInFullscreen(activity);
+ }
+
/**
* Whether camera compat treatment is applicable for the given activity.
*
@@ -292,12 +323,16 @@
* </ul>
*/
boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) {
- return activity != null && !activity.inMultiWindowMode()
+ return activity != null && isCameraActiveInFullscreen(activity)
&& activity.getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED
// "locked" and "nosensor" values are often used by camera apps that can't
// handle dynamic changes so we shouldn't force rotate them.
&& activity.getOverrideOrientation() != SCREEN_ORIENTATION_NOSENSOR
- && activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED
+ && activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED;
+ }
+
+ private boolean isCameraActiveInFullscreen(@NonNull ActivityRecord activity) {
+ return !activity.inMultiWindowMode()
&& mCameraIdPackageBiMap.containsPackageName(activity.packageName)
&& activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
}
@@ -334,7 +369,31 @@
}
mCameraIdPackageBiMap.put(packageName, cameraId);
}
- updateOrientationWithWmLock();
+ ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+ /* considerKeyguardState= */ true);
+ if (topActivity == null || topActivity.getTask() == null) {
+ return;
+ }
+ // Checking whether an activity in fullscreen rather than the task as this camera compat
+ // treatment doesn't cover activity embedding.
+ if (topActivity.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+ if (topActivity.mLetterboxUiController.isOverrideOrientationOnlyForCameraEnabled()) {
+ topActivity.recomputeConfiguration();
+ }
+ updateOrientationWithWmLock();
+ return;
+ }
+ // Checking that the whole app is in multi-window mode as we shouldn't show toast
+ // for the activity embedding case.
+ if (topActivity.getTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
+ showToast(R.string.display_rotation_camera_compat_toast_in_split_screen);
+ }
+ }
+
+ @VisibleForTesting
+ void showToast(@StringRes int stringRes) {
+ UiThread.getHandler().post(
+ () -> Toast.makeText(mWmService.mContext, stringRes, Toast.LENGTH_LONG).show());
}
private synchronized void notifyCameraClosed(@NonNull String cameraId) {
@@ -375,6 +434,17 @@
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d is notified that Camera %s is closed, updating rotation.",
mDisplayContent.mDisplayId, cameraId);
+ ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+ /* considerKeyguardState= */ true);
+ if (topActivity == null
+ // Checking whether an activity in fullscreen rather than the task as this camera
+ // compat treatment doesn't cover activity embedding.
+ || topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+ return;
+ }
+ if (topActivity.mLetterboxUiController.isOverrideOrientationOnlyForCameraEnabled()) {
+ topActivity.recomputeConfiguration();
+ }
updateOrientationWithWmLock();
}
@@ -396,6 +466,10 @@
private final Map<String, String> mPackageToCameraIdMap = new ArrayMap<>();
private final Map<String, String> mCameraIdToPackageMap = new ArrayMap<>();
+ boolean isEmpty() {
+ return mCameraIdToPackageMap.isEmpty();
+ }
+
void put(String packageName, String cameraId) {
// Always using the last connected camera ID for the package even for the concurrent
// camera use case since we can't guess which camera is more important anyway.
diff --git a/services/core/java/com/android/server/wm/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags
index b36f835..031e022 100644
--- a/services/core/java/com/android/server/wm/EventLogTags.logtags
+++ b/services/core/java/com/android/server/wm/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
option java_package com.android.server.wm
@@ -59,6 +59,10 @@
31002 wm_task_moved (TaskId|1|5),(Root Task ID|1|5),(Display Id|1|5),(ToTop|1),(Index|1)
# Task removed with source explanation.
31003 wm_task_removed (TaskId|1|5),(Root Task ID|1|5),(Display Id|1|5),(Reason|3)
+
+# Set the requested orientation of an activity.
+31006 wm_set_requested_orientation (Orientation|1|5),(Component Name|3)
+
# bootanim finished:
31007 wm_boot_animation_done (time|2|3)
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 85938e3..f38ae3f 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -17,7 +17,7 @@
package com.android.server.wm;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
@@ -57,7 +57,7 @@
private Runnable mShowImeRunner;
private boolean mIsImeLayoutDrawn;
private boolean mImeShowing;
- private final InsetsSource mLastSource = new InsetsSource(ITYPE_IME, WindowInsets.Type.ime());
+ private final InsetsSource mLastSource = new InsetsSource(ID_IME, WindowInsets.Type.ime());
/** @see #setFrozen(boolean) */
private boolean mFrozen;
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 1df534f..bd82113 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -25,7 +25,7 @@
import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
@@ -315,12 +315,14 @@
// The caller should not receive the visible insets provided by itself.
if (attrs.type == TYPE_INPUT_METHOD) {
state = new InsetsState(state);
- state.removeSource(ITYPE_IME);
+ state.removeSource(ID_IME);
} else if (attrs.providedInsets != null) {
for (InsetsFrameProvider provider : attrs.providedInsets) {
// TODO(b/234093736): Let InsetsFrameProvider return the public type and the ID.
final int sourceId = provider.type;
- final @InsetsType int type = InsetsState.toPublicType(sourceId);
+ final @InsetsType int type = sourceId == ID_IME
+ ? WindowInsets.Type.ime()
+ : InsetsState.toPublicType(sourceId);
if ((type & WindowInsets.Type.systemBars()) == 0) {
continue;
}
@@ -340,8 +342,7 @@
if (state == originalState) {
state = new InsetsState(state);
}
- final InsetsSource override =
- new InsetsSource(state.getSource(otherProvider.getSource().getId()));
+ final InsetsSource override = new InsetsSource(otherProvider.getSource());
override.setFrame(otherProvider.getOverriddenFrame(windowType));
state.addSource(override);
}
@@ -400,7 +401,7 @@
// During switching tasks with gestural navigation, before the next IME input target
// starts the input, we should adjust and freeze the last IME visibility of the window
// in case delivering obsoleted IME insets state during transitioning.
- final InsetsSource originalImeSource = originalState.peekSource(ITYPE_IME);
+ final InsetsSource originalImeSource = originalState.peekSource(ID_IME);
if (originalImeSource != null) {
final boolean imeVisibility = w.isRequestedVisible(Type.ime());
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 49eaea2..f5af292 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_INSETS;
import static com.android.server.wm.InsetsSourceProviderProto.CAPTURED_LEASH;
@@ -541,7 +541,7 @@
return false;
}
for (int i = 0; i < providers.length; i++) {
- if (providers[i].type == ITYPE_IME) {
+ if (providers[i].type == ID_IME) {
return true;
}
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 455cd48..a3f62b2 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -17,16 +17,19 @@
package com.android.server.wm;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.InsetsSource.ID_IME;
import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.systemGestures;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.server.wm.DisplayContentProto.IME_INSETS_SOURCE_PROVIDER;
+import static com.android.server.wm.DisplayContentProto.INSETS_SOURCE_PROVIDERS;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -34,7 +37,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
-import android.view.InsetsSource;
+import android.util.proto.ProtoOutputStream;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
@@ -93,13 +96,11 @@
InsetsStateController(DisplayContent displayContent) {
mDisplayContent = displayContent;
- mSourceProviderFunc = type -> {
- final InsetsSource source = mState.getSource(type);
- if (type == ITYPE_IME) {
- return new ImeInsetsSourceProvider(source, this, mDisplayContent);
- }
- return new WindowContainerInsetsSourceProvider(source, this, mDisplayContent);
- };
+ mSourceProviderFunc = id -> (id == ID_IME)
+ ? new ImeInsetsSourceProvider(mState.getOrCreateSource(
+ id, ime()), this, mDisplayContent)
+ : new WindowContainerInsetsSourceProvider(mState.getOrCreateSource(
+ id, InsetsState.toPublicType(id)), this, mDisplayContent);
}
InsetsState getRawInsetsState() {
@@ -124,14 +125,14 @@
}
/**
- * @return The provider of a specific type.
+ * @return The provider of a specific source ID.
*/
- WindowContainerInsetsSourceProvider getSourceProvider(@InternalInsetsType int type) {
- return mProviders.computeIfAbsent(type, mSourceProviderFunc);
+ WindowContainerInsetsSourceProvider getSourceProvider(int id) {
+ return mProviders.computeIfAbsent(id, mSourceProviderFunc);
}
ImeInsetsSourceProvider getImeSourceProvider() {
- return (ImeInsetsSourceProvider) getSourceProvider(ITYPE_IME);
+ return (ImeInsetsSourceProvider) getSourceProvider(ID_IME);
}
/**
@@ -216,7 +217,7 @@
// Make sure that we always have a control target for the IME, even if the IME target is
// null. Otherwise there is no leash that will hide it and IME becomes "randomly" visible.
InsetsControlTarget target = imeTarget != null ? imeTarget : mEmptyImeControlTarget;
- onControlChanged(ITYPE_IME, target);
+ onControlChanged(ID_IME, target);
ProtoLog.d(WM_DEBUG_IME, "onImeControlTargetChanged %s",
target != null ? target.getWindow() : "null");
notifyPendingInsetsControlChanged();
@@ -386,4 +387,15 @@
mProviders.valueAt(i).dump(pw, prefix + " ");
}
}
+
+ void dumpDebug(ProtoOutputStream proto, @WindowTraceLogLevel int logLevel) {
+ for (int i = mProviders.size() - 1; i >= 0; i--) {
+ final InsetsSourceProvider provider = mProviders.valueAt(i);
+ provider.dumpDebug(proto,
+ provider.getSource().getType() == ime()
+ ? IME_INSETS_SOURCE_PROVIDER
+ : INSETS_SOURCE_PROVIDERS,
+ logLevel);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 800fe09..7066a33 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -18,14 +18,14 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ALLOW_IGNORE_ORIENTATION_REQUEST;
+import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_CAMERA_COMPAT_TREATMENT;
import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
import android.graphics.Color;
import android.provider.DeviceConfig;
import android.util.Slog;
@@ -309,14 +309,28 @@
mIsDisplayRotationImmersiveAppCompatPolicyEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsDisplayRotationImmersiveAppCompatPolicyEnabled);
mDeviceConfig.updateFlagActiveStatus(
+ /* isActive */ mIsCameraCompatTreatmentEnabled,
+ /* key */ KEY_ENABLE_CAMERA_COMPAT_TREATMENT);
+ mDeviceConfig.updateFlagActiveStatus(
/* isActive */ mIsDisplayRotationImmersiveAppCompatPolicyEnabled,
/* key */ KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY);
+ mDeviceConfig.updateFlagActiveStatus(
+ /* isActive */ true,
+ /* key */ KEY_ALLOW_IGNORE_ORIENTATION_REQUEST);
mLetterboxConfigurationPersister = letterboxConfigurationPersister;
mLetterboxConfigurationPersister.start();
}
/**
+ * Whether enabling ignoreOrientationRequest is allowed on the device. This value is controlled
+ * via {@link android.provider.DeviceConfig}.
+ */
+ boolean isIgnoreOrientationRequestAllowed() {
+ return mDeviceConfig.getFlag(KEY_ALLOW_IGNORE_ORIENTATION_REQUEST);
+ }
+
+ /**
* Overrides the aspect ratio of letterbox for fixed orientation. If given value is <= {@link
* #MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO}, both it and a value of {@link
* com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored and
@@ -1050,38 +1064,8 @@
"enable_translucent_activity_letterbox", false);
}
- @VisibleForTesting
- boolean getPackageManagerProperty(PackageManager pm, String property) {
- boolean enabled = false;
- try {
- final PackageManager.Property p = pm.getProperty(property, mContext.getPackageName());
- enabled = p.getBoolean();
- } catch (PackageManager.NameNotFoundException e) {
- // Property not found
- }
- return enabled;
- }
-
- @VisibleForTesting
- boolean isCompatFakeFocusEnabled(ActivityInfo info) {
- if (!isCompatFakeFocusEnabledOnDevice()) {
- return false;
- }
- // See if the developer has chosen to opt in / out of treatment
- PackageManager pm = mContext.getPackageManager();
- if (getPackageManagerProperty(pm, PROPERTY_COMPAT_FAKE_FOCUS_OPT_OUT)) {
- return false;
- } else if (getPackageManagerProperty(pm, PROPERTY_COMPAT_FAKE_FOCUS_OPT_IN)) {
- return true;
- }
- if (info.isChangeEnabled(ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS)) {
- return true;
- }
- return false;
- }
-
/** Whether fake sending focus is enabled for unfocused apps in splitscreen */
- boolean isCompatFakeFocusEnabledOnDevice() {
+ boolean isCompatFakeFocusEnabled() {
return mIsCompatFakeFocusEnabled
&& DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, true);
@@ -1106,15 +1090,8 @@
/** Whether camera compatibility treatment is enabled. */
boolean isCameraCompatTreatmentEnabled(boolean checkDeviceConfig) {
- return mIsCameraCompatTreatmentEnabled
- && (!checkDeviceConfig || isCameraCompatTreatmentAllowed());
- }
-
- // TODO(b/262977416): Cache a runtime flag and implement
- // DeviceConfig.OnPropertiesChangedListener
- private static boolean isCameraCompatTreatmentAllowed() {
- return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- "enable_compat_camera_treatment", true);
+ return mIsCameraCompatTreatmentEnabled && (!checkDeviceConfig
+ || mDeviceConfig.getFlag(KEY_ENABLE_CAMERA_COMPAT_TREATMENT));
}
/** Whether camera compatibility refresh is enabled. */
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
index cf123a1..d004fa6 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
@@ -33,17 +33,33 @@
final class LetterboxConfigurationDeviceConfig
implements DeviceConfig.OnPropertiesChangedListener {
+ static final String KEY_ENABLE_CAMERA_COMPAT_TREATMENT = "enable_compat_camera_treatment";
+ private static final boolean DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT = true;
+
static final String KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY =
"enable_display_rotation_immersive_app_compat_policy";
private static final boolean DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY =
true;
+ static final String KEY_ALLOW_IGNORE_ORIENTATION_REQUEST =
+ "allow_ignore_orientation_request";
+ private static final boolean DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST = true;
+
@VisibleForTesting
static final Map<String, Boolean> sKeyToDefaultValueMap = Map.of(
+ KEY_ENABLE_CAMERA_COMPAT_TREATMENT,
+ DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT,
KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
- DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY
+ DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
+ KEY_ALLOW_IGNORE_ORIENTATION_REQUEST,
+ DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST
);
+ // Whether camera compatibility treatment is enabled.
+ // See DisplayRotationCompatPolicy for context.
+ private boolean mIsCameraCompatTreatmentEnabled =
+ DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT;
+
// Whether enabling rotation compat policy for immersive apps that prevents auto rotation
// into non-optimal screen orientation while in fullscreen. This is needed because immersive
// apps, such as games, are often not optimized for all orientations and can have a poor UX
@@ -52,6 +68,10 @@
private boolean mIsDisplayRotationImmersiveAppCompatPolicyEnabled =
DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY;
+ // Whether enabling ignoreOrientationRequest is allowed on the device.
+ private boolean mIsAllowIgnoreOrientationRequest =
+ DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST;
+
// Set of active device configs that need to be updated in
// DeviceConfig.OnPropertiesChangedListener#onPropertiesChanged.
private final ArraySet<String> mActiveDeviceConfigsSet = new ArraySet<>();
@@ -91,8 +111,12 @@
*/
boolean getFlag(String key) {
switch (key) {
+ case KEY_ENABLE_CAMERA_COMPAT_TREATMENT:
+ return mIsCameraCompatTreatmentEnabled;
case KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY:
return mIsDisplayRotationImmersiveAppCompatPolicyEnabled;
+ case KEY_ALLOW_IGNORE_ORIENTATION_REQUEST:
+ return mIsAllowIgnoreOrientationRequest;
default:
throw new AssertionError("Unexpected flag name: " + key);
}
@@ -104,10 +128,18 @@
throw new AssertionError("Haven't found default value for flag: " + key);
}
switch (key) {
+ case KEY_ENABLE_CAMERA_COMPAT_TREATMENT:
+ mIsCameraCompatTreatmentEnabled =
+ getDeviceConfig(key, defaultValue);
+ break;
case KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY:
mIsDisplayRotationImmersiveAppCompatPolicyEnabled =
getDeviceConfig(key, defaultValue);
break;
+ case KEY_ALLOW_IGNORE_ORIENTATION_REQUEST:
+ mIsAllowIgnoreOrientationRequest =
+ getDeviceConfig(key, defaultValue);
+ break;
default:
throw new AssertionError("Unexpected flag name: " + key);
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index c5a50ca..8d6de58 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -21,8 +21,10 @@
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
+import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT;
import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
@@ -41,6 +43,7 @@
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
@@ -136,6 +139,8 @@
private final boolean mIsOverrideToNosensorOrientationEnabled;
// Corresponds to OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE
private final boolean mIsOverrideToReverseLandscapeOrientationEnabled;
+ // Corresponds to OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA
+ private final boolean mIsOverrideOrientationOnlyForCameraEnabled;
// Corresponds to OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION
private final boolean mIsOverrideUseDisplayLandscapeNaturalOrientationEnabled;
@@ -149,6 +154,9 @@
// Corresponds to OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION
private final boolean mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled;
+ // Corresponds to OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS
+ private final boolean mIsOverrideEnableCompatFakeFocusEnabled;
+
@Nullable
private final Boolean mBooleanPropertyAllowOrientationOverride;
@Nullable
@@ -204,6 +212,9 @@
@Nullable
private final Boolean mBooleanPropertyIgnoreRequestedOrientation;
+ @Nullable
+ private final Boolean mBooleanPropertyFakeFocus;
+
private boolean mIsRelauchingAfterRequestedOrientationChanged;
LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) {
@@ -218,6 +229,10 @@
readComponentProperty(packageManager, mActivityRecord.packageName,
mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled,
PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION);
+ mBooleanPropertyFakeFocus =
+ readComponentProperty(packageManager, mActivityRecord.packageName,
+ mLetterboxConfiguration::isCompatFakeFocusEnabled,
+ PROPERTY_COMPAT_ENABLE_FAKE_FOCUS);
mBooleanPropertyCameraCompatAllowForceRotation =
readComponentProperty(packageManager, mActivityRecord.packageName,
() -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
@@ -253,6 +268,8 @@
isCompatChangeEnabled(OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE);
mIsOverrideToNosensorOrientationEnabled =
isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR);
+ mIsOverrideOrientationOnlyForCameraEnabled =
+ isCompatChangeEnabled(OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA);
mIsOverrideUseDisplayLandscapeNaturalOrientationEnabled =
isCompatChangeEnabled(OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION);
@@ -265,6 +282,9 @@
mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled =
isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION);
+
+ mIsOverrideEnableCompatFakeFocusEnabled =
+ isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS);
}
/**
@@ -360,6 +380,25 @@
}
/**
+ * Whether sending compat fake focus for split screen resumed activities is enabled. Needed
+ * because some game engines wait to get focus before drawing the content of the app which isn't
+ * guaranteed by default in multi-window modes.
+ *
+ * <p>This treatment is enabled when the following conditions are met:
+ * <ul>
+ * <li>Flag gating the treatment is enabled
+ * <li>Component property is NOT set to false
+ * <li>Component property is set to true or per-app override is enabled
+ * </ul>
+ */
+ boolean shouldSendFakeFocus() {
+ return shouldEnableWithOverrideAndProperty(
+ /* gatingCondition */ mLetterboxConfiguration::isCompatFakeFocusEnabled,
+ mIsOverrideEnableCompatFakeFocusEnabled,
+ mBooleanPropertyFakeFocus);
+ }
+
+ /**
* Sets whether an activity is relaunching after the app has called {@link
* android.app.Activity#setRequestedOrientation}.
*/
@@ -410,6 +449,14 @@
return candidate;
}
+ DisplayContent displayContent = mActivityRecord.mDisplayContent;
+ if (mIsOverrideOrientationOnlyForCameraEnabled && displayContent != null
+ && (displayContent.mDisplayRotationCompatPolicy == null
+ || !displayContent.mDisplayRotationCompatPolicy
+ .isActivityEligibleForOrientationOverride(mActivityRecord))) {
+ return candidate;
+ }
+
if (mIsOverrideToReverseLandscapeOrientationEnabled
&& (isFixedOrientationLandscape(candidate) || mIsOverrideAnyOrientationEnabled)) {
Slog.w(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for "
@@ -439,6 +486,10 @@
return candidate;
}
+ boolean isOverrideOrientationOnlyForCameraEnabled() {
+ return mIsOverrideOrientationOnlyForCameraEnabled;
+ }
+
/**
* Whether activity is eligible for activity "refresh" after camera compat force rotation
* treatment. See {@link DisplayRotationCompatPolicy} for context.
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index db88f0f..b5df3e0 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -799,6 +799,10 @@
if (mDisplayContent.getRotationAnimation() == ScreenRotationAnimation.this) {
// It also invokes kill().
mDisplayContent.setRotationAnimation(null);
+ if (mDisplayContent.mDisplayRotationCompatPolicy != null) {
+ mDisplayContent.mDisplayRotationCompatPolicy
+ .onScreenRotationAnimationFinished();
+ }
} else {
kill();
}
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 210d5a5..6b2bf59 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -99,7 +99,6 @@
}
}
- /** Returns {@code true} if visibility is changed. */
void updateWallpaperWindows(boolean visible) {
boolean changed = false;
if (mVisibleRequested != visible) {
@@ -117,6 +116,10 @@
linkFixedRotationTransform(wallpaperTarget.mToken);
}
}
+ // If wallpaper is in transition, setVisible() will be called from commitVisibility()
+ // when finishing transition. Otherwise commitVisibility() is already called from above
+ // setVisibility().
+ return;
}
final WindowState wallpaperTarget =
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a596eed..b46a720 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -113,6 +113,7 @@
import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN;
import static com.android.server.LockGuard.INDEX_WINDOW;
import static com.android.server.LockGuard.installLock;
+import static com.android.server.policy.PhoneWindowManager.TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
@@ -365,6 +366,7 @@
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowManagerService" : TAG_WM;
+ private static final int TRACE_MAX_SECTION_NAME_LENGTH = 127;
static final int LAYOUT_REPEAT_THRESHOLD = 4;
@@ -4210,7 +4212,8 @@
* <p>Note: this assumes that {@link #mGlobalLock} is held by the caller.
*/
boolean isIgnoreOrientationRequestDisabled() {
- return mIsIgnoreOrientationRequestDisabled;
+ return mIsIgnoreOrientationRequestDisabled
+ || !mLetterboxConfiguration.isIgnoreOrientationRequestAllowed();
}
@Override
@@ -5460,10 +5463,15 @@
case WAITING_FOR_DRAWN_TIMEOUT: {
Runnable callback = null;
- final WindowContainer container = (WindowContainer) msg.obj;
+ final WindowContainer<?> container = (WindowContainer<?>) msg.obj;
synchronized (mGlobalLock) {
ProtoLog.w(WM_ERROR, "Timeout waiting for drawn: undrawn=%s",
container.mWaitingForDrawn);
+ if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+ for (int i = 0; i < container.mWaitingForDrawn.size(); i++) {
+ traceEndWaitingForWindowDrawn(container.mWaitingForDrawn.get(i));
+ }
+ }
container.mWaitingForDrawn.clear();
callback = mWaitingForDrawnCallbacks.remove(container);
}
@@ -6079,10 +6087,16 @@
// Window has been removed or hidden; no draw will now happen, so stop waiting.
ProtoLog.w(WM_DEBUG_SCREEN_ON, "Aborted waiting for drawn: %s", win);
container.mWaitingForDrawn.remove(win);
+ if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+ traceEndWaitingForWindowDrawn(win);
+ }
} else if (win.hasDrawn()) {
// Window is now drawn (and shown).
ProtoLog.d(WM_DEBUG_SCREEN_ON, "Window drawn win=%s", win);
container.mWaitingForDrawn.remove(win);
+ if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+ traceEndWaitingForWindowDrawn(win);
+ }
}
}
if (container.mWaitingForDrawn.isEmpty()) {
@@ -6093,6 +6107,22 @@
});
}
+ private void traceStartWaitingForWindowDrawn(WindowState window) {
+ final String traceName = TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD + "#"
+ + window.getWindowTag();
+ final String shortenedTraceName = traceName.substring(0, Math.min(
+ TRACE_MAX_SECTION_NAME_LENGTH, traceName.length()));
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, shortenedTraceName, /* cookie= */ 0);
+ }
+
+ private void traceEndWaitingForWindowDrawn(WindowState window) {
+ final String traceName = TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD + "#"
+ + window.getWindowTag();
+ final String shortenedTraceName = traceName.substring(0, Math.min(
+ TRACE_MAX_SECTION_NAME_LENGTH, traceName.length()));
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, shortenedTraceName, /* cookie= */ 0);
+ }
+
void requestTraversal() {
mWindowPlacerLocked.requestTraversal();
}
@@ -7811,7 +7841,7 @@
@Override
public void waitForAllWindowsDrawn(Runnable callback, long timeout, int displayId) {
- final WindowContainer container = displayId == INVALID_DISPLAY
+ final WindowContainer<?> container = displayId == INVALID_DISPLAY
? mRoot : mRoot.getDisplayContent(displayId);
if (container == null) {
// The waiting container doesn't exist, no need to wait to run the callback. Run and
@@ -7827,6 +7857,12 @@
if (container.mWaitingForDrawn.isEmpty()) {
allWindowsDrawn = true;
} else {
+ if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+ for (int i = 0; i < container.mWaitingForDrawn.size(); i++) {
+ traceStartWaitingForWindowDrawn(container.mWaitingForDrawn.get(i));
+ }
+ }
+
mWaitingForDrawnCallbacks.put(container, callback);
mH.sendNewMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, container, timeout);
checkDrawnWindowsLocked();
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 29d3462..abdc708 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1036,17 +1036,30 @@
taskDisplayArea.moveRootTaskBehindRootTask(thisTask.getRootTask(), restoreAt);
break;
}
- case HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER:
+ case HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER: {
final Rect insetsProviderWindowContainer = hop.getInsetsProviderFrame();
- final WindowContainer receiverWindowContainer =
+ final WindowContainer container =
WindowContainer.fromBinder(hop.getContainer());
- receiverWindowContainer.addLocalRectInsetsSourceProvider(
+ if (container == null) {
+ Slog.e(TAG, "Attempt to add local insets source provider on unknown: "
+ + container);
+ break;
+ }
+ container.addLocalRectInsetsSourceProvider(
insetsProviderWindowContainer, hop.getInsetsTypes());
break;
- case HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER:
- WindowContainer.fromBinder(hop.getContainer())
- .removeLocalInsetsSourceProvider(hop.getInsetsTypes());
+ }
+ case HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER: {
+ final WindowContainer container =
+ WindowContainer.fromBinder(hop.getContainer());
+ if (container == null) {
+ Slog.e(TAG, "Attempt to remove local insets source provider from unknown: "
+ + container);
+ break;
+ }
+ container.removeLocalInsetsSourceProvider(hop.getInsetsTypes());
break;
+ }
case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: {
final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
if (container == null || container.asDisplayArea() == null
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 31f30af..703cb8a4 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -225,7 +225,6 @@
import android.view.InputWindowHandle;
import android.view.InsetsSource;
import android.view.InsetsState;
-import android.view.InsetsState.InternalInsetsType;
import android.view.Surface;
import android.view.Surface.Rotation;
import android.view.SurfaceControl;
@@ -1531,6 +1530,10 @@
winAnimator.mDrawState = DRAW_PENDING;
if (mActivityRecord != null) {
mActivityRecord.clearAllDrawn();
+ if (mAttrs.type == TYPE_APPLICATION_STARTING
+ && mActivityRecord.mStartingData != null) {
+ mActivityRecord.mStartingData.mIsDisplayed = false;
+ }
}
}
if (!mWmService.mResizingWindows.contains(this)) {
@@ -1696,14 +1699,12 @@
* Returns the insets state for the window and applies the requested visibility.
*/
InsetsState getInsetsStateWithVisibilityOverride() {
- final InsetsState state = new InsetsState(getInsetsState());
- for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
- final boolean requestedVisible = isRequestedVisible(InsetsState.toPublicType(type));
- InsetsSource source = state.peekSource(type);
- if (source != null && source.isVisible() != requestedVisible) {
- source = new InsetsSource(source);
+ final InsetsState state = new InsetsState(getInsetsState(), true /* copySources */);
+ for (int i = state.sourceSize() - 1; i >= 0; i--) {
+ final InsetsSource source = state.sourceAt(i);
+ final boolean requestedVisible = isRequestedVisible(source.getType());
+ if (source.isVisible() != requestedVisible) {
source.setVisible(requestedVisible);
- state.addSource(source);
}
}
return state;
@@ -1882,8 +1883,8 @@
return false;
}
for (int i = mProvidedInsetsSources.size() - 1; i >= 0; i--) {
- final int type = mProvidedInsetsSources.keyAt(i);
- if ((InsetsState.toPublicType(type) & WindowInsets.Type.navigationBars()) != 0) {
+ final InsetsSource source = mProvidedInsetsSources.valueAt(i);
+ if (source.getType() == WindowInsets.Type.navigationBars()) {
return true;
}
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index e661688..e2bdcdd 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -62,9 +62,9 @@
"com_android_server_tv_TvInputHal.cpp",
"com_android_server_vr_VrManagerService.cpp",
"com_android_server_UsbAlsaJackDetector.cpp",
+ "com_android_server_UsbAlsaMidiDevice.cpp",
"com_android_server_UsbDeviceManager.cpp",
"com_android_server_UsbDescriptorParser.cpp",
- "com_android_server_UsbMidiDevice.cpp",
"com_android_server_UsbHostManager.cpp",
"com_android_server_vibrator_VibratorController.cpp",
"com_android_server_vibrator_VibratorManagerService.cpp",
diff --git a/services/core/jni/com_android_server_UsbMidiDevice.cpp b/services/core/jni/com_android_server_UsbAlsaMidiDevice.cpp
similarity index 83%
rename from services/core/jni/com_android_server_UsbMidiDevice.cpp
rename to services/core/jni/com_android_server_UsbAlsaMidiDevice.cpp
index c8e7698..93938b1 100644
--- a/services/core/jni/com_android_server_UsbMidiDevice.cpp
+++ b/services/core/jni/com_android_server_UsbAlsaMidiDevice.cpp
@@ -14,27 +14,25 @@
* limitations under the License.
*/
-#define LOG_TAG "UsbMidiDeviceJNI"
+#define LOG_TAG "UsbAlsaMidiDeviceJNI"
#define LOG_NDEBUG 0
-#include "utils/Log.h"
-
-#include "jni.h"
-#include <nativehelper/JNIPlatformHelp.h>
-#include <nativehelper/ScopedLocalRef.h>
-#include "android_runtime/AndroidRuntime.h"
-#include "android_runtime/Log.h"
-
#include <asm/byteorder.h>
#include <errno.h>
#include <fcntl.h>
+#include <nativehelper/JNIPlatformHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
#include <sound/asound.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
-namespace android
-{
+#include "android_runtime/AndroidRuntime.h"
+#include "android_runtime/Log.h"
+#include "jni.h"
+#include "utils/Log.h"
+
+namespace android {
static jclass sFileDescriptorClass;
static jfieldID sPipeFDField;
@@ -46,10 +44,10 @@
// 1. Input O_RDONLY file descriptor
// 2. Special input file descriptor to block the input thread
// 3. Output O_WRONLY file descriptor
-static jobjectArray android_server_UsbMidiDevice_open(JNIEnv *env, jobject thiz, jint card,
- jint device, jint numInputs,
- jint numOutputs) {
- char path[100];
+static jobjectArray android_server_UsbAlsaMidiDevice_open(JNIEnv *env, jobject thiz, jint card,
+ jint device, jint numInputs,
+ jint numOutputs) {
+ char path[100];
int fd;
snprintf(path, sizeof(path), "/dev/snd/midiC%dD%d", card, device);
@@ -126,9 +124,7 @@
return NULL;
}
-static void
-android_server_UsbMidiDevice_close(JNIEnv *env, jobject thiz, jobjectArray fds)
-{
+static void android_server_UsbAlsaMidiDevice_close(JNIEnv *env, jobject thiz, jobjectArray fds) {
// write to mPipeFD to unblock input thread
jint pipeFD = env->GetIntField(thiz, sPipeFDField);
write(pipeFD, &pipeFD, sizeof(pipeFD));
@@ -144,12 +140,12 @@
static JNINativeMethod method_table[] = {
{"nativeOpen", "(IIII)[Ljava/io/FileDescriptor;",
- (void *)android_server_UsbMidiDevice_open},
- {"nativeClose", "([Ljava/io/FileDescriptor;)V", (void *)android_server_UsbMidiDevice_close},
+ (void *)android_server_UsbAlsaMidiDevice_open},
+ {"nativeClose", "([Ljava/io/FileDescriptor;)V",
+ (void *)android_server_UsbAlsaMidiDevice_close},
};
-int register_android_server_UsbMidiDevice(JNIEnv *env)
-{
+int register_android_server_UsbAlsaMidiDevice(JNIEnv *env) {
jclass clazz = env->FindClass("java/io/FileDescriptor");
if (clazz == NULL) {
ALOGE("Can't find java/io/FileDescriptor");
@@ -157,19 +153,19 @@
}
sFileDescriptorClass = (jclass)env->NewGlobalRef(clazz);
- clazz = env->FindClass("com/android/server/usb/UsbMidiDevice");
+ clazz = env->FindClass("com/android/server/usb/UsbAlsaMidiDevice");
if (clazz == NULL) {
- ALOGE("Can't find com/android/server/usb/UsbMidiDevice");
+ ALOGE("Can't find com/android/server/usb/UsbAlsaMidiDevice");
return -1;
}
sPipeFDField = env->GetFieldID(clazz, "mPipeFD", "I");
if (sPipeFDField == NULL) {
- ALOGE("Can't find UsbMidiDevice.mPipeFD");
+ ALOGE("Can't find UsbAlsaMidiDevice.mPipeFD");
return -1;
}
- return jniRegisterNativeMethods(env, "com/android/server/usb/UsbMidiDevice",
- method_table, NELEM(method_table));
+ return jniRegisterNativeMethods(env, "com/android/server/usb/UsbAlsaMidiDevice", method_table,
+ NELEM(method_table));
}
-};
+}; // namespace android
diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp
index ec42324..db7fced 100644
--- a/services/core/jni/com_android_server_display_DisplayControl.cpp
+++ b/services/core/jni/com_android_server_display_DisplayControl.cpp
@@ -23,9 +23,11 @@
namespace android {
-static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj, jboolean secure) {
+static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj, jboolean secure,
+ jfloat requestedRefreshRate) {
ScopedUtfChars name(env, nameObj);
- sp<IBinder> token(SurfaceComposerClient::createDisplay(String8(name.c_str()), bool(secure)));
+ sp<IBinder> token(SurfaceComposerClient::createDisplay(String8(name.c_str()), bool(secure),
+ requestedRefreshRate));
return javaObjectForIBinder(env, token);
}
@@ -134,7 +136,7 @@
static const JNINativeMethod sDisplayMethods[] = {
// clang-format off
- {"nativeCreateDisplay", "(Ljava/lang/String;Z)Landroid/os/IBinder;",
+ {"nativeCreateDisplay", "(Ljava/lang/String;ZF)Landroid/os/IBinder;",
(void*)nativeCreateDisplay },
{"nativeDestroyDisplay", "(Landroid/os/IBinder;)V",
(void*)nativeDestroyDisplay },
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 00f851f..290ad8d 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -35,8 +35,8 @@
int register_android_server_SerialService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_UsbAlsaJackDetector(JNIEnv* env);
+int register_android_server_UsbAlsaMidiDevice(JNIEnv* env);
int register_android_server_UsbDeviceManager(JNIEnv* env);
-int register_android_server_UsbMidiDevice(JNIEnv* env);
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_vr_VrManagerService(JNIEnv* env);
int register_android_server_vibrator_VibratorController(JavaVM* vm, JNIEnv* env);
@@ -90,8 +90,8 @@
register_android_server_InputManager(env);
register_android_server_LightsService(env);
register_android_server_UsbDeviceManager(env);
- register_android_server_UsbMidiDevice(env);
register_android_server_UsbAlsaJackDetector(env);
+ register_android_server_UsbAlsaMidiDevice(env);
register_android_server_UsbHostManager(env);
register_android_server_vr_VrManagerService(env);
register_android_server_vibrator_VibratorController(vm, env);
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 97dbe04..ab1badb 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -208,11 +208,7 @@
<xs:complexType name="thermalThrottling">
<xs:sequence>
- <xs:element type="brightnessThrottlingMap" name="brightnessThrottlingMap">
- <xs:annotation name="nonnull"/>
- <xs:annotation name="final"/>
- </xs:element>
- <xs:element type="brightnessThrottlingMap" name="concurrentDisplaysBrightnessThrottlingMap">
+ <xs:element type="brightnessThrottlingMap" name="brightnessThrottlingMap" maxOccurs="unbounded">
<xs:annotation name="final"/>
</xs:element>
</xs:sequence>
@@ -225,6 +221,7 @@
<xs:annotation name="final"/>
</xs:element>
</xs:sequence>
+ <xs:attribute name="id" type="xs:string"/>
</xs:complexType>
<xs:complexType name="brightnessThrottlingPoint">
@@ -472,6 +469,14 @@
</xs:complexType>
<xs:complexType name="refreshRateConfigs">
+ <xs:element name="defaultRefreshRate" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <xs:element name="defaultPeakRefreshRate" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
<xs:element name="lowerBlockingZoneConfigs" type="blockingZoneConfig"
minOccurs="0" maxOccurs="1">
<xs:annotation name="final"/>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index aba8a2c..3e1db4c 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -37,6 +37,8 @@
public class BrightnessThrottlingMap {
ctor public BrightnessThrottlingMap();
method @NonNull public final java.util.List<com.android.server.display.config.BrightnessThrottlingPoint> getBrightnessThrottlingPoint();
+ method public String getId();
+ method public void setId(String);
}
public class BrightnessThrottlingPoint {
@@ -188,8 +190,12 @@
public class RefreshRateConfigs {
ctor public RefreshRateConfigs();
+ method public final java.math.BigInteger getDefaultPeakRefreshRate();
+ method public final java.math.BigInteger getDefaultRefreshRate();
method public final com.android.server.display.config.BlockingZoneConfig getHigherBlockingZoneConfigs();
method public final com.android.server.display.config.BlockingZoneConfig getLowerBlockingZoneConfigs();
+ method public final void setDefaultPeakRefreshRate(java.math.BigInteger);
+ method public final void setDefaultRefreshRate(java.math.BigInteger);
method public final void setHigherBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig);
method public final void setLowerBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig);
}
@@ -238,10 +244,7 @@
public class ThermalThrottling {
ctor public ThermalThrottling();
- method @NonNull public final com.android.server.display.config.BrightnessThrottlingMap getBrightnessThrottlingMap();
- method public final com.android.server.display.config.BrightnessThrottlingMap getConcurrentDisplaysBrightnessThrottlingMap();
- method public final void setBrightnessThrottlingMap(@NonNull com.android.server.display.config.BrightnessThrottlingMap);
- method public final void setConcurrentDisplaysBrightnessThrottlingMap(com.android.server.display.config.BrightnessThrottlingMap);
+ method public final java.util.List<com.android.server.display.config.BrightnessThrottlingMap> getBrightnessThrottlingMap();
}
public class ThresholdPoint {
diff --git a/services/core/xsd/display-layout-config/display-layout-config.xsd b/services/core/xsd/display-layout-config/display-layout-config.xsd
index 842d97a..78c9a54 100644
--- a/services/core/xsd/display-layout-config/display-layout-config.xsd
+++ b/services/core/xsd/display-layout-config/display-layout-config.xsd
@@ -51,6 +51,7 @@
<xs:sequence>
<xs:element name="address" type="xs:nonNegativeInteger"/>
<xs:element name="position" type="xs:string" minOccurs="0" maxOccurs="1" />
+ <xs:element name="brightnessThrottlingMapId" type="xs:string" minOccurs="0" maxOccurs="1" />
</xs:sequence>
<xs:attribute name="enabled" type="xs:boolean" use="optional" />
<xs:attribute name="defaultDisplay" type="xs:boolean" use="optional" />
diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt
index 55f866c..6a28d8a 100644
--- a/services/core/xsd/display-layout-config/schema/current.txt
+++ b/services/core/xsd/display-layout-config/schema/current.txt
@@ -4,10 +4,12 @@
public class Display {
ctor public Display();
method public java.math.BigInteger getAddress();
+ method public String getBrightnessThrottlingMapId();
method public String getPosition();
method public boolean isDefaultDisplay();
method public boolean isEnabled();
method public void setAddress(java.math.BigInteger);
+ method public void setBrightnessThrottlingMapId(String);
method public void setDefaultDisplay(boolean);
method public void setEnabled(boolean);
method public void setPosition(String);
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index bb26fa9..ff72ed7 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -29,8 +29,8 @@
import android.credentials.CreateCredentialException;
import android.credentials.CreateCredentialRequest;
import android.credentials.CredentialDescription;
+import android.credentials.CredentialOption;
import android.credentials.GetCredentialException;
-import android.credentials.GetCredentialOption;
import android.credentials.GetCredentialRequest;
import android.credentials.IClearCredentialStateCallback;
import android.credentials.ICreateCredentialCallback;
@@ -49,8 +49,6 @@
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
-import android.service.credentials.BeginCreateCredentialRequest;
-import android.service.credentials.BeginGetCredentialRequest;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.CredentialProviderInfo;
import android.text.TextUtils;
@@ -270,10 +268,10 @@
// All requested credential descriptions based on the given request.
Set<String> requestedCredentialDescriptions =
- request.getGetCredentialOptions().stream().map(
- getCredentialOption -> getCredentialOption
+ request.getCredentialOptions().stream().map(
+ credentialOption -> credentialOption
.getCredentialRetrievalData()
- .getString(GetCredentialOption
+ .getString(CredentialOption
.FLATTENED_REQUEST))
.collect(Collectors.toSet());
@@ -344,11 +342,11 @@
// Initiate all provider sessions
List<ProviderSession> providerSessions =
- initiateProviderSessions(
- session,
- request.getGetCredentialOptions().stream()
- .map(GetCredentialOption::getType)
- .collect(Collectors.toList()));
+ initiateProviderSessions(
+ session,
+ request.getCredentialOptions().stream()
+ .map(CredentialOption::getType)
+ .collect(Collectors.toList()));
if (providerSessions.isEmpty()) {
try {
@@ -364,11 +362,7 @@
}
// Iterate over all provider sessions and invoke the request
- providerSessions.forEach(
- providerGetSession -> providerGetSession
- .getRemoteCredentialService().onBeginGetCredential(
- (BeginGetCredentialRequest) providerGetSession.getProviderRequest(),
- /*callback=*/providerGetSession));
+ providerSessions.forEach(ProviderSession::invokeSession);
return cancelTransport;
}
@@ -412,12 +406,7 @@
// Iterate over all provider sessions and invoke the request
providerSessions.forEach(
- providerCreateSession -> providerCreateSession
- .getRemoteCredentialService()
- .onCreateCredential(
- (BeginCreateCredentialRequest)
- providerCreateSession.getProviderRequest(),
- /* callback= */ providerCreateSession));
+ ProviderSession::invokeSession);
return cancelTransport;
}
@@ -530,14 +519,7 @@
// Iterate over all provider sessions and invoke the request
providerSessions.forEach(
- providerClearSession -> {
- providerClearSession
- .getRemoteCredentialService()
- .onClearCredentialState(
- (android.service.credentials.ClearCredentialStateRequest)
- providerClearSession.getProviderRequest(),
- /* callback= */ providerClearSession);
- });
+ ProviderSession::invokeSession);
return cancelTransport;
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index 48e35b2..b112649 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -115,4 +115,11 @@
ProviderPendingIntentResponse providerPendingIntentResponse) {
//Not applicable for clearCredential as response is not picked by the user
}
+
+ @Override
+ protected void invokeSession() {
+ this.mRemoteCredentialService.onClearCredentialState(
+ this.getProviderRequest(),
+ /*callback=*/this);
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 7a24a22..cc5a8ab 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -197,6 +197,13 @@
}
}
+ @Override
+ protected void invokeSession() {
+ this.mRemoteCredentialService.onCreateCredential(
+ this.getProviderRequest(),
+ /*callback=*/this);
+ }
+
private List<Entry> prepareUiSaveEntries(@NonNull List<CreateEntry> saveEntries) {
Log.i(TAG, "in populateUiSaveEntries");
List<Entry> uiSaveEntries = new ArrayList<>();
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 95f2313..dec3432 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -21,8 +21,8 @@
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.Intent;
+import android.credentials.CredentialOption;
import android.credentials.GetCredentialException;
-import android.credentials.GetCredentialOption;
import android.credentials.GetCredentialResponse;
import android.credentials.ui.Entry;
import android.credentials.ui.GetCredentialProviderData;
@@ -107,7 +107,7 @@
) {
return new BeginGetCredentialRequest.Builder(callingAppInfo)
.setBeginGetCredentialOptions(
- filteredRequest.getGetCredentialOptions().stream().map(
+ filteredRequest.getCredentialOptions().stream().map(
option -> {
return new BeginGetCredentialOption(
option.getType(),
@@ -121,8 +121,8 @@
List<String> providerCapabilities,
android.credentials.GetCredentialRequest clientRequest
) {
- List<GetCredentialOption> filteredOptions = new ArrayList<>();
- for (GetCredentialOption option : clientRequest.getGetCredentialOptions()) {
+ List<CredentialOption> filteredOptions = new ArrayList<>();
+ for (CredentialOption option : clientRequest.getCredentialOptions()) {
if (providerCapabilities.contains(option.getType())) {
Log.i(TAG, "In createProviderRequest - capability found : "
+ option.getType());
@@ -135,7 +135,7 @@
if (!filteredOptions.isEmpty()) {
return new android.credentials.GetCredentialRequest
.Builder(clientRequest.getData())
- .setGetCredentialOptions(
+ .setCredentialOptions(
filteredOptions).build();
}
Log.i(TAG, "In createProviderRequest - returning null");
@@ -230,6 +230,13 @@
}
}
+ @Override
+ protected void invokeSession() {
+ this.mRemoteCredentialService.onBeginGetCredential(
+ this.getProviderRequest(),
+ /*callback=*/this);
+ }
+
@Override // Call from request session to data to be shown on the UI
@Nullable protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
Log.i(TAG, "In prepareUiData");
@@ -300,7 +307,7 @@
private Intent setUpFillInIntent(String type) {
Intent intent = new Intent();
- for (GetCredentialOption option : mCompleteRequest.getGetCredentialOptions()) {
+ for (CredentialOption option : mCompleteRequest.getCredentialOptions()) {
if (option.getType().equals(type)) {
intent.putExtra(
CredentialProviderService
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 678c752..8e0d6f8 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -208,4 +208,8 @@
/** Should be overridden to handle the selected entry from the UI. */
protected abstract void onUiEntrySelected(String entryType, String entryId,
ProviderPendingIntentResponse providerPendingIntentResponse);
+
+ /** Should be overridden to invoke the provider at a defined location. Helpful for
+ * situations such as metric generation. */
+ protected abstract void invokeSession();
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 36cb898..d7a2095 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -1441,8 +1441,7 @@
if (mManagedSubscriptionsPolicy != null) {
pw.println("mManagedSubscriptionsPolicy:");
pw.increaseIndent();
- pw.print("mPolicyType=");
- mManagedSubscriptionsPolicy.getPolicyType();
+ pw.println(mManagedSubscriptionsPolicy);
pw.decreaseIndent();
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9c64054..c6b2207 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -18435,9 +18435,9 @@
.addExtras(extras)
.build();
- mInjector.getNotificationManager().notifyAsUser(
+ mHandler.post(() -> mInjector.getNotificationManager().notifyAsUser(
null, SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED, notification,
- UserHandle.of(getProfileParentId(profileUserId)));
+ UserHandle.of(getProfileParentId(profileUserId))));
}
private String getPersonalAppSuspensionButtonText() {
@@ -20254,6 +20254,17 @@
DevicePolicyManager.MTE_ENABLED);
Preconditions.checkArgument(
allowedModes.contains(flags), "Provided mode is not one of the allowed values.");
+ // In general, this API should be available when "bootctl_settings_toggle" is set, which
+ // signals that there is a control for MTE in the user settings and this API fundamentally
+ // is a way for the device admin to override that setting.
+ // Allow bootctl_device_policy_manager as an override, e.g. to offer the
+ // DevicePolicyManager only without a visible user setting.
+ if (!mInjector.systemPropertiesGetBoolean(
+ "ro.arm64.memtag.bootctl_device_policy_manager",
+ mInjector.systemPropertiesGetBoolean(
+ "ro.arm64.memtag.bootctl_settings_toggle", false))) {
+ throw new UnsupportedOperationException("device does not support MTE");
+ }
final CallerIdentity caller = getCallerIdentity();
if (flags == DevicePolicyManager.MTE_DISABLED) {
Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
index f2cff62..a26b2ac 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
@@ -71,6 +71,10 @@
return opNameMapToOpIntMap(getUidModes(uid))
}
+ override fun getNonDefaultPackageModes(packageName: String, userId: Int): SparseIntArray {
+ return opNameMapToOpIntMap(getPackageModes(packageName, userId))
+ }
+
override fun getUidMode(uid: Int, op: Int): Int {
val appId = UserHandle.getAppId(uid)
val userId = UserHandle.getUserId(uid)
diff --git a/services/proguard.flags b/services/proguard.flags
index ba4560f..c133044 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -103,7 +103,7 @@
-keep,allowoptimization,allowaccessmodification class com.android.server.storage.AppFuseBridge { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.tv.TvInputHal { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbAlsaJackDetector { *; }
--keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbMidiDevice { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbAlsaMidiDevice { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorController$OnVibrationCompleteListener { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorManagerService$OnSyncedVibrationCompleteListener { *; }
-keepclasseswithmembers,allowoptimization,allowaccessmodification class com.android.server.** {
diff --git a/services/searchui/java/com/android/server/searchui/SearchUiManagerService.java b/services/searchui/java/com/android/server/searchui/SearchUiManagerService.java
index 524002a..29ad537 100644
--- a/services/searchui/java/com/android/server/searchui/SearchUiManagerService.java
+++ b/services/searchui/java/com/android/server/searchui/SearchUiManagerService.java
@@ -125,6 +125,26 @@
(service) -> service.queryLocked(sessionId, query, callback));
}
+ public void registerEmptyQueryResultUpdateCallback(@NonNull SearchSessionId sessionId,
+ @NonNull ISearchCallback callback) {
+ runForUserLocked("registerEmptyQueryResultUpdateCallback", sessionId,
+ (service) -> service.registerEmptyQueryResultUpdateCallbackLocked(sessionId,
+ callback));
+ }
+
+ public void unregisterEmptyQueryResultUpdateCallback(@NonNull SearchSessionId sessionId,
+ @NonNull ISearchCallback callback) {
+ runForUserLocked("unregisterEmptyQueryResultUpdateCallback", sessionId,
+ (service) -> service.unregisterEmptyQueryResultUpdateCallbackLocked(sessionId,
+ callback));
+ }
+
+ @Override
+ public void requestEmptyQueryResultUpdate(@NonNull SearchSessionId sessionId) {
+ runForUserLocked("requestEmptyQueryResultUpdate", sessionId,
+ (service) -> service.requestEmptyQueryResultUpdateLocked(sessionId));
+ }
+
@Override
public void destroySearchSession(@NonNull SearchSessionId sessionId) {
runForUserLocked("destroySearchSession", sessionId,
diff --git a/services/searchui/java/com/android/server/searchui/SearchUiPerUserService.java b/services/searchui/java/com/android/server/searchui/SearchUiPerUserService.java
index 4c31978..0d70fff 100644
--- a/services/searchui/java/com/android/server/searchui/SearchUiPerUserService.java
+++ b/services/searchui/java/com/android/server/searchui/SearchUiPerUserService.java
@@ -149,6 +149,48 @@
}
/**
+ * Registers a callback for continuous updates of search targets for empty query result used for
+ * zero state.
+ */
+ @GuardedBy("mLock")
+ public void registerEmptyQueryResultUpdateCallbackLocked(@NonNull SearchSessionId sessionId,
+ @NonNull ISearchCallback callback) {
+ final SearchSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (sessionInfo == null) return;
+ final boolean serviceExists = resolveService(sessionId,
+ s -> s.onRegisterEmptyQueryResultUpdateCallback(sessionId, callback));
+ if (serviceExists) {
+ sessionInfo.addCallbackLocked(callback);
+ }
+ }
+
+ /**
+ * Unregisters a callback for continuous updates of search targets for empty query result
+ * used for zero state.
+ */
+ @GuardedBy("mLock")
+ public void unregisterEmptyQueryResultUpdateCallbackLocked(@NonNull SearchSessionId sessionId,
+ @NonNull ISearchCallback callback) {
+ final SearchSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (sessionInfo == null) return;
+ final boolean serviceExists = resolveService(sessionId,
+ s -> s.onUnregisterEmptyQueryResultUpdateCallback(sessionId, callback));
+ if (serviceExists) {
+ sessionInfo.removeCallbackLocked(callback);
+ }
+ }
+
+ /**
+ * Requests a new set of search targets for empty query result used for zero state.
+ */
+ @GuardedBy("mLock")
+ public void requestEmptyQueryResultUpdateLocked(@NonNull SearchSessionId sessionId) {
+ final SearchSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (sessionInfo == null) return;
+ resolveService(sessionId, s->s.onRequestEmptyQueryResultUpdate(sessionId));
+ }
+
+ /**
* Notifies the service of the end of an existing search session.
*/
@GuardedBy("mLock")
@@ -310,18 +352,7 @@
final IBinder.DeathRecipient mDeathRecipient;
private final RemoteCallbackList<ISearchCallback> mCallbacks =
- new RemoteCallbackList<ISearchCallback>() {
- @Override
- public void onCallbackDied(ISearchCallback callback) {
- if (DEBUG) {
- Slog.d(TAG, "Binder died for session Id=" + mSessionId
- + " and callback=" + callback.asBinder());
- }
- if (mCallbacks.getRegisteredCallbackCount() == 0) {
- destroy();
- }
- }
- };
+ new RemoteCallbackList<>();
SearchSessionInfo(
@NonNull final SearchSessionId id,
@@ -337,6 +368,22 @@
mDeathRecipient = deathRecipient;
}
+ void addCallbackLocked(ISearchCallback callback) {
+ if (DEBUG) {
+ Slog.d(TAG, "Storing callback for session Id=" + mSessionId
+ + " and callback=" + callback.asBinder());
+ }
+ mCallbacks.register(callback);
+ }
+
+ void removeCallbackLocked(ISearchCallback callback) {
+ if (DEBUG) {
+ Slog.d(TAG, "Removing callback for session Id=" + mSessionId
+ + " and callback=" + callback.asBinder());
+ }
+ mCallbacks.unregister(callback);
+ }
+
boolean linkToDeath() {
try {
mToken.linkToDeath(mDeathRecipient, 0);
@@ -369,6 +416,9 @@
+ callbackCount + " callbacks.");
}
service.onCreateSearchSessionLocked(mSearchContext, mSessionId, token);
+ mCallbacks.broadcast(
+ callback -> service.registerEmptyQueryResultUpdateCallbackLocked(mSessionId,
+ callback));
}
}
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 720f486..7e44049 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -34,6 +34,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import android.os.Binder;
import android.os.RemoteException;
import android.view.inputmethod.InputMethodManager;
@@ -66,8 +67,8 @@
@Test
public void testPerformShowIme() throws Exception {
synchronized (ImfLock.class) {
- mVisibilityApplier.performShowIme(mWindowToken, null /* statsToken */,
- InputMethodManager.SHOW_IMPLICIT, null, SHOW_SOFT_INPUT);
+ mVisibilityApplier.performShowIme(new Binder() /* showInputToken */,
+ null /* statsToken */, InputMethodManager.SHOW_IMPLICIT, null, SHOW_SOFT_INPUT);
}
verifyShowSoftInput(false, true, InputMethodManager.SHOW_IMPLICIT);
}
@@ -75,8 +76,8 @@
@Test
public void testPerformHideIme() throws Exception {
synchronized (ImfLock.class) {
- mVisibilityApplier.performHideIme(mWindowToken, null /* statsToken */, null,
- HIDE_SOFT_INPUT);
+ mVisibilityApplier.performHideIme(new Binder() /* hideInputToken */,
+ null /* statsToken */, null, HIDE_SOFT_INPUT);
}
verifyHideSoftInput(false, true);
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 75484d1..a39e021 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -1129,6 +1129,7 @@
false /*allowInstall*/,
false /*instantApp*/,
false /*virtualPreload*/,
+ false /* stopped */,
UserManagerService.getInstance(),
null /*usesSdkLibraries*/,
null /*usesSdkLibrariesVersions*/,
@@ -1173,6 +1174,7 @@
true /*allowInstall*/,
false /*instantApp*/,
false /*virtualPreload*/,
+ false /* stopped */,
UserManagerService.getInstance(),
null /*usesSdkLibraries*/,
null /*usesSdkLibrariesVersions*/,
@@ -1217,6 +1219,7 @@
false /*allowInstall*/,
false /*instantApp*/,
false /*virtualPreload*/,
+ false /* stopped */,
UserManagerService.getInstance(),
null /*usesSdkLibraries*/,
null /*usesSdkLibrariesVersions*/,
@@ -1262,6 +1265,7 @@
false /*allowInstall*/,
false /*instantApp*/,
false /*virtualPreload*/,
+ false /* stopped */,
UserManagerService.getInstance(),
null /*usesSdkLibraries*/,
null /*usesSdkLibrariesVersions*/,
@@ -1284,6 +1288,48 @@
verifyUserState(userState, false /*notLaunched*/, false /*stopped*/, true /*installed*/);
}
+ /** Create a new stopped system PackageSetting */
+ @Test
+ public void testCreateNewSetting05() {
+ final PackageSetting testPkgSetting01 = Settings.createNewSetting(
+ PACKAGE_NAME,
+ null /*originalPkg*/,
+ null /*disabledPkg*/,
+ null /*realPkgName*/,
+ null /*sharedUser*/,
+ UPDATED_CODE_PATH /*codePath*/,
+ null /*legacyNativeLibraryPath*/,
+ "arm64-v8a" /*primaryCpuAbi*/,
+ "armeabi" /*secondaryCpuAbi*/,
+ UPDATED_VERSION_CODE /*versionCode*/,
+ ApplicationInfo.FLAG_SYSTEM /*pkgFlags*/,
+ 0 /*pkgPrivateFlags*/,
+ UserHandle.SYSTEM /*installUser*/,
+ false /*allowInstall*/,
+ false /*instantApp*/,
+ false /*virtualPreload*/,
+ true /* stopped */,
+ UserManagerService.getInstance(),
+ null /*usesSdkLibraries*/,
+ null /*usesSdkLibrariesVersions*/,
+ null /*usesStaticLibraries*/,
+ null /*usesStaticLibrariesVersions*/,
+ null /*mimeGroups*/,
+ UUID.randomUUID());
+ assertThat(testPkgSetting01.getAppId(), is(0));
+ assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
+ assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
+ assertThat(testPkgSetting01.getFlags(), is(ApplicationInfo.FLAG_SYSTEM));
+ assertThat(testPkgSetting01.getPrivateFlags(), is(0));
+ assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
+ assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
+ assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
+ assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("armeabi"));
+ assertThat(testPkgSetting01.getVersionCode(), is(UPDATED_VERSION_CODE));
+ final PackageUserState userState = testPkgSetting01.readUserState(0);
+ verifyUserState(userState, false /*notLaunched*/, true /*stopped*/, true /*installed*/);
+ }
+
@Test
public void testSetPkgStateLibraryFiles_addNewFiles() {
final PackageSetting packageSetting = createPackageSetting("com.foo");
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index ef470fe..3b20735 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -112,7 +112,6 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
import android.Manifest;
import android.app.ActivityManager;
@@ -1265,7 +1264,30 @@
mService.interactiveStateChangedLocked(false);
mService.interactiveStateChangedLocked(true);
runnableCaptor.getValue().run();
- verify(mMockContext).sendBroadcastAsUser(mService.mTimeTickIntent, UserHandle.ALL);
+ final ArgumentCaptor<Bundle> optionsCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mMockContext).sendBroadcastAsUser(eq(mService.mTimeTickIntent), eq(UserHandle.ALL),
+ isNull(), optionsCaptor.capture());
+ verifyTimeTickBroadcastOptions(optionsCaptor.getValue());
+ }
+
+ @Test
+ public void sendsTimeTickOnAlarmTrigger() throws Exception {
+ final ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ // Stubbing so the handler doesn't actually run the runnable.
+ doReturn(true).when(mService.mHandler).post(runnableCaptor.capture());
+ mService.mTimeTickTrigger.doAlarm(mock(IAlarmCompleteListener.class));
+ runnableCaptor.getValue().run();
+ final ArgumentCaptor<Bundle> optionsCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mMockContext).sendBroadcastAsUser(eq(mService.mTimeTickIntent), eq(UserHandle.ALL),
+ isNull(), optionsCaptor.capture());
+ verifyTimeTickBroadcastOptions(optionsCaptor.getValue());
+ }
+
+ private void verifyTimeTickBroadcastOptions(Bundle actualOptionsBundle) {
+ final BroadcastOptions actualOptions = new BroadcastOptions(actualOptionsBundle);
+ assertEquals(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT,
+ actualOptions.getDeliveryGroupPolicy());
+ assertTrue(actualOptions.isDeferUntilActive());
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 41ae50b..5e4ba88 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -1401,10 +1401,10 @@
// Confirm that we saw no registered receiver traffic
final IApplicationThread oldThread = oldApp.getThread();
verify(oldThread, never()).scheduleRegisteredReceiver(any(), any(), anyInt(), any(), any(),
- anyBoolean(), anyBoolean(), anyBoolean(), anyInt(), anyInt());
+ anyBoolean(), anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyInt(), any());
final IApplicationThread newThread = newApp.getThread();
verify(newThread, never()).scheduleRegisteredReceiver(any(), any(), anyInt(), any(), any(),
- anyBoolean(), anyBoolean(), anyBoolean(), anyInt(), anyInt());
+ anyBoolean(), anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyInt(), any());
// Confirm that we saw final manifest broadcast
verifyScheduleReceiver(times(1), newApp, airplane,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index c40017a..9f7b72c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -110,6 +110,7 @@
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -225,6 +226,31 @@
}
}
+ /**
+ * Replace the process LRU with the given processes.
+ * @param apps
+ */
+ private void setProcessesToLru(ProcessRecord... apps) {
+ ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
+ lru.clear();
+ Collections.addAll(lru, apps);
+ }
+
+ /**
+ * Run updateOomAdjLocked().
+ * - If there's only one process, then it calls updateOomAdjLocked(ProcessRecord, int).
+ * - Otherwise, sets the processes to the LRU and run updateOomAdjLocked(int).
+ */
+ private void updateOomAdj(ProcessRecord... apps) {
+ if (apps.length == 1) {
+ sService.mOomAdjuster.updateOomAdjLocked(apps[0], OomAdjuster.OOM_ADJ_REASON_NONE);
+ } else {
+ setProcessesToLru(apps);
+ sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mProcessList.getLruProcessesLOSP().clear();
+ }
+ }
+
@SuppressWarnings("GuardedBy")
@Test
public void testUpdateOomAdj_DoOne_Persistent_TopUi_Sleeping() {
@@ -233,7 +259,7 @@
app.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
app.mState.setHasTopUi(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_ASLEEP);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERSISTENT_PROC_ADJ,
@@ -248,7 +274,7 @@
app.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
app.mState.setHasTopUi(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_PERSISTENT_UI, PERSISTENT_PROC_ADJ,
SCHED_GROUP_TOP_APP);
@@ -262,7 +288,7 @@
app.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
doReturn(app).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
doReturn(null).when(sService).getTopApp();
assertProcStates(app, PROCESS_STATE_PERSISTENT_UI, PERSISTENT_PROC_ADJ,
@@ -277,7 +303,7 @@
doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
doReturn(app).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
doReturn(null).when(sService).getTopApp();
assertProcStates(app, PROCESS_STATE_TOP, FOREGROUND_APP_ADJ, SCHED_GROUP_TOP_APP);
@@ -291,7 +317,7 @@
doReturn(PROCESS_STATE_TOP_SLEEPING).when(sService.mAtmInternal).getTopProcessState();
app.mState.setRunningRemoteAnimation(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
assertProcStates(app, PROCESS_STATE_TOP_SLEEPING, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP);
@@ -304,7 +330,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
doReturn(mock(ActiveInstrumentation.class)).when(app).getActiveInstrumentation();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
doCallRealMethod().when(app).getActiveInstrumentation();
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, FOREGROUND_APP_ADJ,
@@ -319,7 +345,7 @@
doReturn(true).when(sService).isReceivingBroadcastLocked(any(ProcessRecord.class),
any(int[].class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
doReturn(false).when(sService).isReceivingBroadcastLocked(any(ProcessRecord.class),
any(int[].class));
@@ -333,7 +359,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
app.mServices.startExecutingService(mock(ServiceRecord.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_SERVICE, FOREGROUND_APP_ADJ, SCHED_GROUP_BACKGROUND);
}
@@ -346,7 +372,7 @@
doReturn(PROCESS_STATE_TOP_SLEEPING).when(sService.mAtmInternal).getTopProcessState();
doReturn(app).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_ASLEEP);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
doReturn(null).when(sService).getTopApp();
doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
@@ -363,7 +389,7 @@
app.mState.setCurRawAdj(CACHED_APP_MIN_ADJ);
doReturn(null).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, CACHED_APP_MIN_ADJ,
SCHED_GROUP_BACKGROUND);
@@ -389,7 +415,7 @@
})).when(wpc).computeOomAdjFromActivities(
any(WindowProcessController.ComputeOomAdjCallback.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP);
}
@@ -403,7 +429,7 @@
doReturn(true).when(wpc).hasRecentTasks();
app.mState.setLastTopTime(SystemClock.uptimeMillis());
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
doCallRealMethod().when(wpc).hasRecentTasks();
assertEquals(PROCESS_STATE_CACHED_RECENT, app.mState.getSetProcState());
@@ -417,7 +443,7 @@
app.mServices.setHasForegroundServices(true, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION,
/* hasNoneType=*/false);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -430,7 +456,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
app.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -458,7 +484,7 @@
app.mState.setLastTopTime(SystemClock.uptimeMillis());
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
@@ -477,7 +503,7 @@
- sService.mConstants.TOP_TO_FGS_GRACE_DURATION);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
PERCEPTIBLE_MEDIUM_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
@@ -503,7 +529,7 @@
- sService.mConstants.TOP_TO_FGS_GRACE_DURATION);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
// Procstate should be lower than FGS. (It should be SERVICE)
assertEquals(app.mState.getSetProcState(), PROCESS_STATE_SERVICE);
@@ -520,7 +546,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
app.mState.setHasOverlayUi(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -534,7 +560,7 @@
app.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
app.mState.setLastTopTime(SystemClock.uptimeMillis());
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE,
PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ, SCHED_GROUP_DEFAULT);
@@ -558,7 +584,7 @@
s.getConnections().clear();
app.mServices.updateHasTopStartedAlmostPerceptibleServices();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
}
@@ -578,7 +604,7 @@
nowUptime - 2 * sService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs;
app.mServices.updateHasTopStartedAlmostPerceptibleServices();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
}
@@ -599,11 +625,31 @@
s.getConnections().clear();
app.mServices.updateHasTopStartedAlmostPerceptibleServices();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertNotEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
}
}
+
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testUpdateOomAdj_DoOne_ImpFg_AlmostPerceptibleService() {
+ ProcessRecord system = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+ MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, true));
+ system.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ system.mState.setHasTopUi(true);
+ // Simulate the system starting and binding to a service in the app.
+ ServiceRecord s = bindService(app, system,
+ null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
+ sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+
+ assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
+ PERCEPTIBLE_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
+ }
+
@SuppressWarnings("GuardedBy")
@Test
public void testUpdateOomAdj_DoOne_Toast() {
@@ -611,7 +657,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
app.mState.setForcingToImportant(new Object());
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -625,7 +671,7 @@
WindowProcessController wpc = app.getWindowProcessController();
doReturn(true).when(wpc).isHeavyWeightProcess();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
doReturn(false).when(wpc).isHeavyWeightProcess();
assertProcStates(app, PROCESS_STATE_HEAVY_WEIGHT, HEAVY_WEIGHT_APP_ADJ,
@@ -640,7 +686,7 @@
WindowProcessController wpc = app.getWindowProcessController();
doReturn(true).when(wpc).isHomeProcess();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_HOME, HOME_APP_ADJ, SCHED_GROUP_BACKGROUND);
}
@@ -654,7 +700,7 @@
doReturn(true).when(wpc).isPreviousProcess();
doReturn(true).when(wpc).hasActivities();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
SCHED_GROUP_BACKGROUND);
@@ -669,7 +715,7 @@
backupTarget.app = app;
doReturn(backupTarget).when(sService.mBackupTargets).get(anyInt());
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
doReturn(null).when(sService.mBackupTargets).get(anyInt());
assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, BACKUP_APP_ADJ,
@@ -683,7 +729,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
app.mServices.setHasClientActivities(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertEquals(PROCESS_STATE_CACHED_ACTIVITY_CLIENT, app.mState.getSetProcState());
}
@@ -695,7 +741,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
app.mServices.setTreatLikeActivity(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertEquals(PROCESS_STATE_CACHED_ACTIVITY, app.mState.getSetProcState());
}
@@ -712,7 +758,7 @@
s.lastActivity = SystemClock.uptimeMillis();
app.mServices.startService(s);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_B_ADJ, SCHED_GROUP_BACKGROUND);
}
@@ -724,7 +770,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
app.mState.setMaxAdj(PERCEPTIBLE_LOW_APP_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, PERCEPTIBLE_LOW_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -739,7 +785,7 @@
app.mState.setCurRawAdj(SERVICE_ADJ);
doReturn(null).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertTrue(ProcessList.CACHED_APP_MIN_ADJ <= app.mState.getSetAdj());
assertTrue(ProcessList.CACHED_APP_MAX_ADJ >= app.mState.getSetAdj());
@@ -756,7 +802,7 @@
s.lastActivity = SystemClock.uptimeMillis();
app.mServices.startService(s);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND);
}
@@ -774,7 +820,7 @@
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
doReturn(client).when(sService).getTopApp();
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
doReturn(null).when(sService).getTopApp();
assertProcStates(app, PROCESS_STATE_SERVICE, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
@@ -790,7 +836,7 @@
bindService(app, client, null, Context.BIND_WAIVE_PRIORITY
| Context.BIND_TREAT_LIKE_ACTIVITY, mock(IBinder.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(PROCESS_STATE_CACHED_ACTIVITY, app.mState.getSetProcState());
}
@@ -810,7 +856,7 @@
mock(ActivityServiceConnectionsHolder.class));
doReturn(true).when(cr.activity).isActivityVisible();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
assertEquals(SCHED_GROUP_TOP_APP_BOUND, app.mState.getSetSchedGroup());
@@ -822,11 +868,8 @@
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
bindService(app, app, null, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
}
@@ -841,7 +884,7 @@
client.mServices.setTreatLikeActivity(true);
bindService(app, client, null, 0, mock(IBinder.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(PROCESS_STATE_CACHED_EMPTY, app.mState.getSetProcState());
}
@@ -861,7 +904,7 @@
doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
doReturn(client).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
doReturn(null).when(sService).getTopApp();
assertEquals(PREVIOUS_APP_ADJ, app.mState.getSetAdj());
@@ -878,7 +921,7 @@
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
client.mState.setHasTopUi(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -894,7 +937,7 @@
bindService(app, client, null, Context.BIND_IMPORTANT, mock(IBinder.class));
client.mServices.startExecutingService(mock(ServiceRecord.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
}
@@ -910,7 +953,7 @@
doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
doReturn(client).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
doReturn(null).when(sService).getTopApp();
assertProcStates(app, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_DEFAULT);
@@ -926,9 +969,10 @@
bindService(app, client, null, Context.BIND_FOREGROUND_SERVICE, mock(IBinder.class));
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(PROCESS_STATE_BOUND_FOREGROUND_SERVICE, app.mState.getSetProcState());
+ assertEquals(PROCESS_STATE_PERSISTENT, client.mState.getSetProcState());
}
@SuppressWarnings("GuardedBy")
@@ -941,7 +985,7 @@
bindService(app, client, null, Context.BIND_NOT_FOREGROUND, mock(IBinder.class));
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, app.mState.getSetProcState());
}
@@ -956,7 +1000,7 @@
bindService(app, client, null, 0, mock(IBinder.class));
client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(PROCESS_STATE_FOREGROUND_SERVICE, app.mState.getSetProcState());
}
@@ -973,13 +1017,14 @@
backupTarget.app = client;
doReturn(backupTarget).when(sService.mBackupTargets).get(anyInt());
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
+
doReturn(null).when(sService.mBackupTargets).get(anyInt());
assertEquals(BACKUP_APP_ADJ, app.mState.getSetAdj());
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertEquals(PERSISTENT_SERVICE_ADJ, app.mState.getSetAdj());
}
@@ -994,7 +1039,7 @@
bindService(app, client, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class));
client.mState.setRunningRemoteAnimation(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertEquals(PERCEPTIBLE_LOW_APP_ADJ, app.mState.getSetAdj());
}
@@ -1009,7 +1054,7 @@
bindService(app, client, null, Context.BIND_NOT_VISIBLE, mock(IBinder.class));
client.mState.setRunningRemoteAnimation(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(PERCEPTIBLE_APP_ADJ, app.mState.getSetAdj());
}
@@ -1024,23 +1069,25 @@
bindService(app, client, null, 0, mock(IBinder.class));
client.mState.setHasOverlayUi(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(PERCEPTIBLE_APP_ADJ, app.mState.getSetAdj());
}
@SuppressWarnings("GuardedBy")
@Test
- public void testUpdateOomAdj_DoOne_Service_MediumPerceptible() {
+ public void testUpdateOomAdj_DoOne_Service_AlmostPerceptible() {
{
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
- bindService(app, client, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
+ bindService(app, client, null,
+ Context.BIND_ALMOST_PERCEPTIBLE | Context.BIND_NOT_FOREGROUND,
+ mock(IBinder.class));
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
}
@@ -1052,13 +1099,48 @@
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
WindowProcessController wpc = client.getWindowProcessController();
doReturn(true).when(wpc).isHeavyWeightProcess();
- bindService(app, client, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
+ bindService(app, client, null,
+ Context.BIND_ALMOST_PERCEPTIBLE | Context.BIND_NOT_FOREGROUND,
+ mock(IBinder.class));
+ client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ updateOomAdj(client, app);
+ doReturn(false).when(wpc).isHeavyWeightProcess();
+
+ assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
+ }
+
+ {
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+ ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+ MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+ bindService(app, client, null,
+ Context.BIND_ALMOST_PERCEPTIBLE,
+ mock(IBinder.class));
+ client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+
+ assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
+ }
+
+ {
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+ ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+ MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+ WindowProcessController wpc = client.getWindowProcessController();
+ doReturn(true).when(wpc).isHeavyWeightProcess();
+ bindService(app, client, null,
+ Context.BIND_ALMOST_PERCEPTIBLE,
+ mock(IBinder.class));
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
doReturn(false).when(wpc).isHeavyWeightProcess();
- assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
+ assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
}
}
@@ -1072,7 +1154,7 @@
bindService(app, client, null, 0, mock(IBinder.class));
client.mState.setRunningRemoteAnimation(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(VISIBLE_APP_ADJ, app.mState.getSetAdj());
}
@@ -1087,7 +1169,7 @@
bindService(app, client, null, Context.BIND_IMPORTANT_BACKGROUND, mock(IBinder.class));
client.mState.setHasOverlayUi(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(PROCESS_STATE_IMPORTANT_BACKGROUND, app.mState.getSetProcState());
}
@@ -1098,7 +1180,7 @@
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
bindProvider(app, app, null, null, false);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
}
@@ -1112,12 +1194,8 @@
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindProvider(app, client, null, null, false);
client.mServices.setTreatLikeActivity(true);
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client);
assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
}
@@ -1133,7 +1211,7 @@
doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
doReturn(client).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
doReturn(null).when(sService).getTopApp();
assertProcStates(app, PROCESS_STATE_BOUND_TOP, FOREGROUND_APP_ADJ, SCHED_GROUP_DEFAULT);
@@ -1149,7 +1227,7 @@
client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindProvider(app, client, null, null, false);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1164,7 +1242,7 @@
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
bindProvider(app, client, null, null, true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND, FOREGROUND_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1177,7 +1255,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
app.mProviders.setLastProviderTime(SystemClock.uptimeMillis());
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
SCHED_GROUP_BACKGROUND);
@@ -1197,7 +1275,7 @@
doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
doReturn(client2).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, client2, app);
doReturn(null).when(sService).getTopApp();
assertProcStates(app, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ,
@@ -1217,7 +1295,7 @@
bindService(app, client2, null, 0, mock(IBinder.class));
client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, client2, app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1236,7 +1314,7 @@
bindService(client, client2, null, 0, mock(IBinder.class));
client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, client2, app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1255,13 +1333,12 @@
bindService(client, client2, null, 0, mock(IBinder.class));
client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindService(client2, app, null, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
+
+ // Note: We add processes to LRU but still call updateOomAdjLocked() with a specific
+ // processes.
+ setProcessesToLru(app, client, client2);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1292,13 +1369,8 @@
MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
bindService(client2, client, null, 0, mock(IBinder.class));
client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client, client2);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1321,13 +1393,8 @@
bindService(client, client2, null, 0, mock(IBinder.class));
bindService(client2, client, null, 0, mock(IBinder.class));
client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client, client2);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1357,15 +1424,8 @@
bindService(client3, client4, null, 0, mock(IBinder.class));
bindService(client4, client3, null, 0, mock(IBinder.class));
client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
- lru.add(client3);
- lru.add(client4);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client, client2, client3, client4);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1396,14 +1456,8 @@
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
client3.mState.setForcingToImportant(new Object());
bindService(app, client3, null, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
- lru.add(client3);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client, client2, client3);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1427,14 +1481,8 @@
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
client3.mState.setForcingToImportant(new Object());
bindService(app, client3, null, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
- lru.add(client3);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client, client2, client3);
assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1460,15 +1508,8 @@
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
client4.mState.setForcingToImportant(new Object());
bindService(app, client4, null, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
- lru.add(client3);
- lru.add(client4);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client, client2, client3, client4);
assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1496,15 +1537,8 @@
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
client4.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindService(app, client4, null, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
- lru.add(client3);
- lru.add(client4);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client, client2, client3, client4);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1529,7 +1563,7 @@
client3.mState.setForcingToImportant(new Object());
bindService(app, client3, null, 0, mock(IBinder.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, client2, client3, app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1548,7 +1582,7 @@
bindProvider(client, client2, null, null, false);
client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, client2, app);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1567,13 +1601,8 @@
bindProvider(client, client2, null, null, false);
client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindService(client2, app, null, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client, client2);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1592,7 +1621,7 @@
bindProvider(client, client2, null, null, false);
client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, client2, app);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1611,14 +1640,8 @@
bindProvider(client, client2, null, null, false);
client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindProvider(client2, app, null, null, false);
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
-
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client, client2);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1643,8 +1666,7 @@
client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
- sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client1, client2, app1, app2);
assertProcStates(app1, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1653,8 +1675,7 @@
bindService(app1, client1, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class));
bindService(app2, client2, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class));
- sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
- sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app1, app2);
assertProcStates(app1, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
SCHED_GROUP_TOP_APP);
@@ -1662,8 +1683,7 @@
SCHED_GROUP_DEFAULT);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_ASLEEP);
- sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
- sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client1, client2, app1, app2);
assertProcStates(app1, PROCESS_STATE_IMPORTANT_FOREGROUND, VISIBLE_APP_ADJ,
SCHED_GROUP_TOP_APP);
assertProcStates(app2, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1689,8 +1709,7 @@
final ServiceRecord s2 = bindService(app2, client2, null,
Context.BIND_IMPORTANT, mock(IBinder.class));
- sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
- sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client1, client2, app1, app2);
assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1699,7 +1718,7 @@
bindService(app2, client1, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
mock(IBinder.class));
- sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app2);
assertProcStates(app2, PROCESS_STATE_PERSISTENT, PERSISTENT_SERVICE_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1715,10 +1734,10 @@
bindService(app2, client2, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
mock(IBinder.class));
- sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
- sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app1, app2);
- assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+ // VISIBLE_APP_ADJ is the max oom-adj for BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE.
+ assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
assertProcStates(app2, PROCESS_STATE_IMPORTANT_FOREGROUND, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1747,7 +1766,7 @@
bindService(app1, client1, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class));
- sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client1, app1);
assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1767,7 +1786,7 @@
bindService(app1, client1, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
- sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(client1, app1);
assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1798,13 +1817,7 @@
client1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
client2.mState.setForcingToImportant(new Object());
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app1);
- lru.add(app2);
- lru.add(app3);
- lru.add(client1);
- lru.add(client2);
+ setProcessesToLru(app1, app2, app3, client1, client2);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
final ComponentName cn1 = ComponentName.unflattenFromString(
@@ -1891,13 +1904,9 @@
ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
app2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(app2);
+
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
- lru.clear();
+ updateOomAdj(app, app2);
assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1915,13 +1924,8 @@
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
app2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindService(app, app2, null, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(app2);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
- lru.clear();
+ updateOomAdj(app, app2);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1942,14 +1946,8 @@
bindService(app2, app3, null, 0, mock(IBinder.class));
app3.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindService(app3, app, null, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(app2);
- lru.add(app3);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
- lru.clear();
+ updateOomAdj(app, app2, app3);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -1990,16 +1988,8 @@
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindService(app, app5, s, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(app2);
- lru.add(app3);
- lru.add(app4);
- lru.add(app5);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
- lru.clear();
+ updateOomAdj(app, app2, app3, app4, app5);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -2035,16 +2025,8 @@
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindService(app, app5, s, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app5);
- lru.add(app4);
- lru.add(app3);
- lru.add(app2);
- lru.add(app);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
- lru.clear();
+ updateOomAdj(app5, app4, app3, app2, app);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -2080,16 +2062,8 @@
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindService(app, app5, s, 0, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app3);
- lru.add(app4);
- lru.add(app2);
- lru.add(app);
- lru.add(app5);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
- lru.clear();
+ updateOomAdj(app3, app4, app2, app, app5);
assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -2119,18 +2093,13 @@
MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
client3.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
bindService(app, client3, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class));
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(client);
- lru.add(client2);
- lru.add(client3);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdj(app, client, client2, client3);
- assertEquals(PROCESS_CAPABILITY_ALL, client.mState.getSetCapability());
- assertEquals(PROCESS_CAPABILITY_ALL, client2.mState.getSetCapability());
- assertEquals(PROCESS_CAPABILITY_ALL, app.mState.getSetCapability());
+ final int expected = PROCESS_CAPABILITY_ALL;
+ assertEquals(expected, client.mState.getSetCapability());
+ assertEquals(expected, client2.mState.getSetCapability());
+ assertEquals(expected, app.mState.getSetCapability());
}
@SuppressWarnings("GuardedBy")
@@ -2155,16 +2124,8 @@
MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
bindProvider(app, app5, cr, null, false);
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app);
- lru.add(app2);
- lru.add(app3);
- lru.add(app4);
- lru.add(app5);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
- lru.clear();
+ updateOomAdj(app, app2, app3, app4, app5);
assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -2202,15 +2163,9 @@
doCallRealMethod().when(s).getConnections();
s.startRequested = true;
s.lastActivity = now;
- ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
- lru.clear();
- lru.add(app3);
- lru.add(app2);
- lru.add(app);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
sService.mOomAdjuster.mNumServiceProcs = 3;
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
- lru.clear();
+ updateOomAdj(app3, app2, app);
assertEquals(SERVICE_B_ADJ, app3.mState.getSetAdj());
assertEquals(SERVICE_ADJ, app2.mState.getSetAdj());
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index 9eed6ad..d1f7f93 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -42,6 +42,7 @@
import android.content.res.AssetManager;
import android.os.Handler;
import android.os.UserHandle;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -51,12 +52,14 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.internal.util.ArrayUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.server.LocalServices;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.pm.pkg.PackageStateInternal;
import org.junit.After;
import org.junit.Before;
@@ -188,6 +191,11 @@
// Stub out package calls to disable AppOpsService#updatePermissionRevokedCompat
doReturn(null).when(mPackageManager).getPackagesForUid(anyInt());
+
+ doReturn(new ArrayMap<String, PackageStateInternal>()).when(mPackageManagerInternal)
+ .getPackageStates();
+
+ doReturn(new int[] {0}).when(mUserManagerInternal).getUserIds();
}
@After
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 3d36c1c..57e873d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -16,13 +16,17 @@
package com.android.server.display;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -46,8 +50,11 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.LocalServices;
+import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
+import com.android.server.display.color.ColorDisplayService;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.testutils.OffsettableClock;
@@ -58,7 +65,9 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
import java.util.List;
@@ -67,8 +76,9 @@
@RunWith(AndroidJUnit4.class)
public final class DisplayPowerController2Test {
private static final String UNIQUE_DISPLAY_ID = "unique_id_test123";
- private static final int DISPLAY_ID = 42;
+ private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
+ private MockitoSession mSession;
private OffsettableClock mClock;
private TestLooper mTestLooper;
private Handler mHandler;
@@ -105,13 +115,20 @@
private DualRampAnimator<DisplayPowerState> mDualRampAnimatorMock;
@Mock
private WakelockController mWakelockController;
+ @Mock
+ private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
@Captor
private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
+ mSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .spyStatic(LocalServices.class)
+ .spyStatic(BatteryStatsService.class)
+ .startMocking();
mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
@@ -156,10 +173,17 @@
when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock);
when(mContextSpy.getResources()).thenReturn(mResourcesMock);
+
+ doAnswer((Answer<ColorDisplayService.ColorDisplayServiceInternal>) invocationOnMock ->
+ mCdsiMock).when(() -> LocalServices.getService(
+ ColorDisplayService.ColorDisplayServiceInternal.class));
+ doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
+ BatteryStatsService.getService());
}
@After
public void tearDown() {
+ mSession.finishMocking();
LocalServices.removeServiceForTest(WindowManagerPolicy.class);
}
@@ -207,6 +231,32 @@
WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
}
+ @Test
+ public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() throws Exception {
+ setUpDisplay(1, UNIQUE_DISPLAY_ID);
+
+ Sensor proxSensor = setUpProxSensor();
+
+ DisplayPowerController2 dpc = new DisplayPowerController2(
+ mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
+ mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
+ mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
+ }, mHighBrightnessModeMetadataMock);
+
+ when(mDisplayPowerStateMock.getScreenState()).thenReturn(Display.STATE_ON);
+ // send a display power request
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ dpr.useProximitySensor = true;
+ dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+ // Run updatePowerState
+ advanceTime(1);
+
+ verify(mSensorManagerMock, never()).registerListener(any(SensorEventListener.class),
+ eq(proxSensor), anyInt(), any(Handler.class));
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
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 b6388cc..6bf5b62 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -16,14 +16,18 @@
package com.android.server.display;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -46,8 +50,11 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.LocalServices;
+import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
+import com.android.server.display.color.ColorDisplayService;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.testutils.OffsettableClock;
@@ -58,7 +65,9 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
import java.util.List;
@@ -67,8 +76,9 @@
@RunWith(AndroidJUnit4.class)
public final class DisplayPowerControllerTest {
private static final String UNIQUE_DISPLAY_ID = "unique_id_test123";
- private static final int DISPLAY_ID = 42;
+ private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
+ private MockitoSession mSession;
private OffsettableClock mClock;
private TestLooper mTestLooper;
private Handler mHandler;
@@ -103,13 +113,20 @@
private DisplayPowerState mDisplayPowerStateMock;
@Mock
private DualRampAnimator<DisplayPowerState> mDualRampAnimatorMock;
+ @Mock
+ private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
@Captor
private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
+ mSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .spyStatic(LocalServices.class)
+ .spyStatic(BatteryStatsService.class)
+ .startMocking();
mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
@@ -138,10 +155,17 @@
when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock);
when(mContextSpy.getResources()).thenReturn(mResourcesMock);
+
+ doAnswer((Answer<ColorDisplayService.ColorDisplayServiceInternal>) invocationOnMock ->
+ mCdsiMock).when(() -> LocalServices.getService(
+ ColorDisplayService.ColorDisplayServiceInternal.class));
+ doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
+ BatteryStatsService.getService());
}
@After
public void tearDown() {
+ mSession.finishMocking();
LocalServices.removeServiceForTest(WindowManagerPolicy.class);
}
@@ -189,6 +213,32 @@
dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
}
+ @Test
+ public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() throws Exception {
+ setUpDisplay(1, UNIQUE_DISPLAY_ID);
+
+ Sensor proxSensor = setUpProxSensor();
+
+ DisplayPowerController dpc = new DisplayPowerController(
+ mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
+ mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
+ mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
+ }, mHighBrightnessModeMetadataMock);
+
+ when(mDisplayPowerStateMock.getScreenState()).thenReturn(Display.STATE_ON);
+ // send a display power request
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ dpr.useProximitySensor = true;
+ dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+ // Run updatePowerState
+ advanceTime(1);
+
+ verify(mSensorManagerMock, never()).registerListener(any(SensorEventListener.class),
+ eq(proxSensor), anyInt(), any(Handler.class));
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index ed78e72..c7bacbc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -32,6 +32,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
@@ -470,6 +472,50 @@
}
/**
+ * Confirm that
+ * returns {@code null} when for user-visible jobs stopped by the user.
+ */
+ @Test
+ public void testGetRescheduleJobForFailure_userStopped() {
+ JobStatus uiJob = createJobStatus("testGetRescheduleJobForFailure",
+ createJobInfo().setUserInitiated(true));
+ JobStatus uvJob = createJobStatus("testGetRescheduleJobForFailure", createJobInfo());
+ spyOn(uvJob);
+ doReturn(true).when(uvJob).isUserVisibleJob();
+ JobStatus regJob = createJobStatus("testGetRescheduleJobForFailure", createJobInfo());
+
+ // Reschedule for a non-user reason
+ JobStatus rescheduledUiJob = mService.getRescheduleJobForFailureLocked(uiJob,
+ JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
+ JobStatus rescheduledUvJob = mService.getRescheduleJobForFailureLocked(uvJob,
+ JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
+ JobStatus rescheduledRegJob = mService.getRescheduleJobForFailureLocked(regJob,
+ JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
+ assertNotNull(rescheduledUiJob);
+ assertNotNull(rescheduledUvJob);
+ assertNotNull(rescheduledRegJob);
+
+ // Reschedule for a user reason. The user-visible jobs shouldn't be rescheduled.
+ spyOn(rescheduledUvJob);
+ doReturn(true).when(rescheduledUvJob).isUserVisibleJob();
+ rescheduledUiJob = mService.getRescheduleJobForFailureLocked(rescheduledUiJob,
+ JobParameters.STOP_REASON_USER,
+ JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP);
+ rescheduledUvJob = mService.getRescheduleJobForFailureLocked(rescheduledUvJob,
+ JobParameters.STOP_REASON_USER,
+ JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP);
+ rescheduledRegJob = mService.getRescheduleJobForFailureLocked(rescheduledRegJob,
+ JobParameters.STOP_REASON_USER,
+ JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP);
+ assertNull(rescheduledUiJob);
+ assertNull(rescheduledUvJob);
+ assertNotNull(rescheduledRegJob);
+ }
+
+ /**
* Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
* with the correct delay and deadline constraints if the periodic job is scheduled with the
* minimum possible period.
@@ -1274,14 +1320,14 @@
mService.getJobStore().add(job2a);
mService.getJobStore().add(job2b);
- mService.stopUserVisibleJobsInternal("pkg1", 1);
+ mService.notePendingUserRequestedAppStopInternal("pkg1", 1, "test");
assertEquals(4, mService.getPendingJobQueue().size());
assertTrue(mService.getPendingJobQueue().contains(job1a));
assertTrue(mService.getPendingJobQueue().contains(job1b));
assertTrue(mService.getPendingJobQueue().contains(job2a));
assertTrue(mService.getPendingJobQueue().contains(job2b));
- mService.stopUserVisibleJobsInternal("pkg1", 0);
+ mService.notePendingUserRequestedAppStopInternal("pkg1", 0, "test");
assertEquals(2, mService.getPendingJobQueue().size());
assertFalse(mService.getPendingJobQueue().contains(job1a));
assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job1a));
@@ -1290,7 +1336,7 @@
assertTrue(mService.getPendingJobQueue().contains(job2a));
assertTrue(mService.getPendingJobQueue().contains(job2b));
- mService.stopUserVisibleJobsInternal("pkg2", 0);
+ mService.notePendingUserRequestedAppStopInternal("pkg2", 0, "test");
assertEquals(0, mService.getPendingJobQueue().size());
assertFalse(mService.getPendingJobQueue().contains(job1a));
assertFalse(mService.getPendingJobQueue().contains(job1b));
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 2c47fd9..e6bc72f 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
@@ -225,6 +225,33 @@
}
@Test
+ public void testIsUserVisibleJob() {
+ JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setUserInitiated(false)
+ .build();
+ JobStatus job = createJobStatus(jobInfo);
+
+ assertFalse(job.isUserVisibleJob());
+
+ // User-initiated jobs are always user-visible unless they've been demoted.
+ jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setUserInitiated(true)
+ .build();
+ job = createJobStatus(jobInfo);
+
+ assertTrue(job.isUserVisibleJob());
+
+ job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
+ assertFalse(job.isUserVisibleJob());
+
+ job.startedAsUserInitiatedJob = true;
+ assertTrue(job.isUserVisibleJob());
+
+ job.startedAsUserInitiatedJob = false;
+ assertFalse(job.isUserVisibleJob());
+ }
+
+ @Test
public void testMediaBackupExemption_lateConstraint() {
final JobInfo triggerContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT)
.addTriggerContentUri(new JobInfo.TriggerContentUri(IMAGES_MEDIA_URI, 0))
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
index 01ce696..8979585 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
@@ -20,6 +20,8 @@
import static android.view.Display.INVALID_DISPLAY;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
import static com.android.server.pm.UserVisibilityChangedEvent.onInvisible;
import static com.android.server.pm.UserVisibilityChangedEvent.onVisible;
@@ -73,8 +75,8 @@
assertUserCanBeAssignedExtraDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
// Make sure another user cannot be started on default display
- int result2 = mMediator.assignUserToDisplayOnStart(otherUserId, visibleBgUserId,
- BG_VISIBLE, DEFAULT_DISPLAY);
+ int result2 = mMediator.assignUserToDisplayOnStart(otherUserId, otherUserId, BG_VISIBLE,
+ DEFAULT_DISPLAY);
assertStartUserResult(result2, USER_ASSIGNMENT_RESULT_FAILURE,
"when user (%d) is starting on default display after it was started by user %d",
otherUserId, visibleBgUserId);
@@ -117,8 +119,8 @@
assertUserCanBeAssignedExtraDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
// Make sure another user cannot be started on default display
- int result2 = mMediator.assignUserToDisplayOnStart(otherUserId, visibleBgUserId,
- BG_VISIBLE, DEFAULT_DISPLAY);
+ int result2 = mMediator.assignUserToDisplayOnStart(otherUserId, otherUserId, BG_VISIBLE,
+ DEFAULT_DISPLAY);
assertStartUserResult(result2, USER_ASSIGNMENT_RESULT_FAILURE,
"when user (%d) is starting on default display after it was started by user %d",
otherUserId, visibleBgUserId);
@@ -126,4 +128,102 @@
listener.verify();
}
+
+ @Test
+ public void
+ testStartVisibleBgProfile_onDefaultDisplay_whenParentIsStartedVisibleOnBgOnSecondaryDisplay()
+ throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
+ startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+ BG_VISIBLE, DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+ expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+ expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+ expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, PARENT_USER_ID);
+
+ assertInvisibleUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+
+ listener.verify();
+ }
+
+ @Test
+ public void
+ testStartVisibleBgProfile_onDefaultDisplay_whenParentIsStartedVisibleOnBgOnDefaultDisplay()
+ throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(
+ onVisible(PARENT_USER_ID),
+ onVisible(PROFILE_USER_ID));
+ startUserInSecondaryDisplay(PARENT_USER_ID, DEFAULT_DISPLAY);
+
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+ BG_VISIBLE, DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+ // Assert parent user visibility
+ expectUserIsVisible(PARENT_USER_ID);
+ expectUserIsVisibleOnDisplay(PARENT_USER_ID, DEFAULT_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(PARENT_USER_ID, INVALID_DISPLAY);
+ expectDisplayAssignedToUser(PARENT_USER_ID, DEFAULT_DISPLAY);
+ expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
+ assertUserCanBeAssignedExtraDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+
+ // Assert profile user visibility
+ expectUserIsVisible(PROFILE_USER_ID);
+ expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
+ // Only full user (parent) is assigned to the display
+ expectDisplayAssignedToUser(PROFILE_USER_ID, INVALID_DISPLAY);
+ assertUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+
+ listener.verify();
+ }
+
+ @Test
+ public void testStartBgProfile_onDefaultDisplay_whenParentIsStartedVisibleOnBg()
+ throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
+ startUserInSecondaryDisplay(PARENT_USER_ID, DEFAULT_DISPLAY);
+
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+ DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
+
+ // Assert parent user visibility
+ expectUserIsVisible(PARENT_USER_ID);
+ expectUserIsVisibleOnDisplay(PARENT_USER_ID, DEFAULT_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(PARENT_USER_ID, INVALID_DISPLAY);
+ expectDisplayAssignedToUser(PARENT_USER_ID, DEFAULT_DISPLAY);
+ expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
+ assertUserCanBeAssignedExtraDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+
+ // Assert profile user visibility
+ expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+ expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+ assertUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+ listener.verify();
+ }
+
+ @Test
+ public void testStartVisibleBgUser_onDefaultDisplay_currentUserId() throws Exception {
+ int currentUserId = INITIAL_CURRENT_USER_ID;
+
+ AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+ int result = mMediator.assignUserToDisplayOnStart(currentUserId, currentUserId,
+ BG_VISIBLE, DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE);
+
+ // Assert current user visibility
+ expectUserIsVisible(currentUserId);
+ expectUserIsVisibleOnDisplay(currentUserId, DEFAULT_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(currentUserId, INVALID_DISPLAY);
+ expectDisplayAssignedToUser(currentUserId, DEFAULT_DISPLAY);
+
+ assertUserCanBeAssignedExtraDisplay(currentUserId, OTHER_SECONDARY_DISPLAY_ID);
+
+ listener.verify();
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index 5176d68..566084a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -44,7 +44,6 @@
import android.util.IntArray;
import android.util.Log;
-import com.android.internal.util.Preconditions;
import com.android.server.ExtendedMockitoTestCase;
import org.junit.Before;
@@ -149,6 +148,12 @@
}
@Test
+ public final void testAssignUserToDisplayOnStart_invalidUserStartMode() {
+ assertThrows(IllegalArgumentException.class, () -> mMediator
+ .assignUserToDisplayOnStart(USER_ID, USER_ID, 666, DEFAULT_DISPLAY));
+ }
+
+ @Test
public final void testStartFgUser_onSecondaryDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
@@ -283,7 +288,7 @@
int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
BG_VISIBLE, DEFAULT_DISPLAY);
- assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
expectNoDisplayAssignedToUser(PROFILE_USER_ID);
@@ -299,14 +304,14 @@
int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
BG_VISIBLE, DEFAULT_DISPLAY);
- assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
expectNoDisplayAssignedToUser(PROFILE_USER_ID);
expectInitialCurrentUserAssignedToDisplay(DEFAULT_DISPLAY);
- assertUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+ assertInvisibleUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
listener.verify();
}
@@ -332,6 +337,41 @@
}
@Test
+ public final void testStartBgProfile_onDefaultDisplay_whenParentIsNotStarted()
+ throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+ DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
+
+ expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+ expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testStartBgProfile_onDefaultDisplay_whenParentIsStartedOnBg()
+ throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForNoEvents();
+ startBackgroundUser(PARENT_USER_ID);
+
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+ DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
+
+ expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+
+ expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+ expectInitialCurrentUserAssignedToDisplay(DEFAULT_DISPLAY);
+
+ assertUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+
+ listener.verify();
+ }
+
+ @Test
public final void testStartBgProfile_onSecondaryDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
@@ -485,8 +525,6 @@
* se.
*/
protected final void startUserInSecondaryDisplay(@UserIdInt int userId, int displayId) {
- Preconditions.checkArgument(displayId != INVALID_DISPLAY && displayId != DEFAULT_DISPLAY,
- "must pass a secondary display, not %d", displayId);
Log.d(TAG, "startUserInSecondaryDisplay(" + userId + ", " + displayId + ")");
int result = mMediator.assignUserToDisplayOnStart(userId, userId, BG_VISIBLE, displayId);
if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
index 49c6a88..f084063 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
@@ -108,34 +108,6 @@
}
@Test
- public final void testStartVisibleBgProfile_onDefaultDisplay_whenParentIsCurrentUser()
- throws Exception {
- AsyncUserVisibilityListener listener = addListenerForEvents(
- onInvisible(INITIAL_CURRENT_USER_ID),
- onVisible(PARENT_USER_ID),
- onVisible(PROFILE_USER_ID));
- startForegroundUser(PARENT_USER_ID);
-
- int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
- BG_VISIBLE, DEFAULT_DISPLAY);
- assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
- expectUserCannotBeUnassignedFromDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
-
- expectUserIsVisible(PROFILE_USER_ID);
- expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
- expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
- expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
- expectVisibleUsers(PARENT_USER_ID, PROFILE_USER_ID);
-
- expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY);
- expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
-
- assertUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
-
- listener.verify();
- }
-
- @Test
public final void testStartFgUser_onInvalidDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
@@ -268,14 +240,83 @@
}
@Test
+ public final void testStartVisibleBgProfile_onDefaultDisplay_whenParentIsCurrentUser()
+ throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(
+ onInvisible(INITIAL_CURRENT_USER_ID),
+ onVisible(PARENT_USER_ID),
+ onVisible(PROFILE_USER_ID));
+ startForegroundUser(PARENT_USER_ID);
+
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+ BG_VISIBLE, DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+ expectUserCannotBeUnassignedFromDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
+
+ expectUserIsVisible(PROFILE_USER_ID);
+ expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
+ expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+ expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
+ expectVisibleUsers(PARENT_USER_ID, PROFILE_USER_ID);
+
+ expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY);
+ expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
+
+ assertUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+
+ listener.verify();
+ }
+
+ @Test
public final void
- testStartVisibleBgProfile_onDefaultDisplay_whenParentVisibleOnSecondaryDisplay()
- throws Exception {
+ testStartVisibleBgProfile_onDefaultDisplay_whenParentIsStartedVisibleOnAnotherDisplay()
+ throws Exception {
AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
BG_VISIBLE, DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+ expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+ expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+ expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, PARENT_USER_ID);
+
+ assertInvisibleUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+
+ listener.verify();
+ }
+
+ // Not supported - profiles can only be started on default display
+ @Test
+ public final void
+ testStartVisibleBgProfile_onSecondaryDisplay_whenParentIsStartedVisibleOnThatDisplay()
+ throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
+ startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+ BG_VISIBLE, DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+ expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+ expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+ expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, PARENT_USER_ID);
+
+ assertInvisibleUserCannotBeAssignedExtraDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void
+ testStartProfile_onDefaultDisplay_whenParentIsStartedVisibleOnSecondaryDisplay()
+ throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
+ startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
+ DEFAULT_DISPLAY);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
index 863dcb6..a02807f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
@@ -24,6 +24,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -70,7 +71,6 @@
WindowMagnificationManagerStub mWindowMagnificationManagerStub;
@Before
-
public void setup() {
MockitoAnnotations.initMocks(this);
mFullScreenMagnificationControllerStub = new FullScreenMagnificationControllerStub(
@@ -433,27 +433,60 @@
eq(newConfig), anyBoolean(), anyInt());
}
+ @Test
+ public void setMagnificationConfigWithActivatedFalse_fullScreenEnabled_resetMagnification() {
+ setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN);
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setActivated(false).build();
+ mMagnificationProcessor.setMagnificationConfig(TEST_DISPLAY, config, false, SERVICE_ID);
+
+ verify(mMockFullScreenMagnificationController).reset(eq(TEST_DISPLAY), anyBoolean());
+ }
+
+ @Test
+ public void setMagnificationConfigWithActivatedFalse_windowEnabled_disableMagnification() {
+ setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_WINDOW);
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setActivated(false).build();
+ mMagnificationProcessor.setMagnificationConfig(TEST_DISPLAY, config, false, SERVICE_ID);
+
+ verify(mMockWindowMagnificationManager)
+ .disableWindowMagnification(eq(TEST_DISPLAY), anyBoolean());
+ }
+
+ @Test
+ public void setMagnificationConfigWithActivatedFalse_expectedReturnedValue() {
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setActivated(false).build();
+ assertFalse(mMagnificationProcessor.setMagnificationConfig(
+ TEST_DISPLAY, config, false, SERVICE_ID));
+ }
+
private void setMagnificationActivated(int displayId, int configMode) {
setMagnificationActivated(displayId,
- new MagnificationConfig.Builder().setMode(configMode).build());
+ new MagnificationConfig.Builder()
+ .setMode(configMode)
+ .setScale(TEST_SCALE).build());
}
private void setMagnificationActivated(int displayId, MagnificationConfig config) {
+ if (!config.isActivated()) {
+ fail("setMagnificationActivated method should be called with config activated true");
+ }
+
when(mMockMagnificationController.isActivated(displayId, config.getMode())).thenReturn(
true);
mMagnificationProcessor.setMagnificationConfig(displayId, config, false, SERVICE_ID);
if (config.getMode() == MAGNIFICATION_MODE_FULLSCREEN) {
- when(mMockMagnificationController.isActivated(displayId,
- MAGNIFICATION_MODE_WINDOW)).thenReturn(false);
mFullScreenMagnificationControllerStub.resetAndStubMethods();
mMockFullScreenMagnificationController.setScaleAndCenter(displayId, config.getScale(),
config.getCenterX(), config.getCenterY(), false, SERVICE_ID);
+ mWindowMagnificationManagerStub.deactivateIfNeed();
} else if (config.getMode() == MAGNIFICATION_MODE_WINDOW) {
- when(mMockMagnificationController.isActivated(displayId,
- MAGNIFICATION_MODE_FULLSCREEN)).thenReturn(false);
mWindowMagnificationManagerStub.resetAndStubMethods();
mMockWindowMagnificationManager.enableWindowMagnification(displayId, config.getScale(),
config.getCenterX(), config.getCenterY());
+ mFullScreenMagnificationControllerStub.deactivateIfNeed();
}
}
@@ -469,6 +502,7 @@
private float mScale = 1.0f;
private float mCenterX = 0;
private float mCenterY = 0;
+ private boolean mIsActivated = false;
private boolean mIsRegistered = false;
FullScreenMagnificationControllerStub(
@@ -485,7 +519,11 @@
TEST_DISPLAY);
doAnswer(invocation -> mIsRegistered).when(mScreenMagnificationController).isRegistered(
TEST_DISPLAY);
+ doAnswer(invocation -> mIsActivated).when(mScreenMagnificationController).isActivated(
+ TEST_DISPLAY);
+
Answer enableMagnificationStubAnswer = invocation -> {
+ mIsActivated = true;
mScale = invocation.getArgument(1);
mCenterX = invocation.getArgument(2);
mCenterY = invocation.getArgument(3);
@@ -495,6 +533,13 @@
mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), anyFloat(),
anyFloat(), anyFloat(), anyBoolean(), eq(SERVICE_ID));
+ Answer disableMagnificationStubAnswer = invocation -> {
+ deactivateIfNeed();
+ return true;
+ };
+ doAnswer(disableMagnificationStubAnswer).when(
+ mScreenMagnificationController).reset(eq(TEST_DISPLAY), anyBoolean());
+
Answer registerStubAnswer = invocation -> {
mIsRegistered = true;
return true;
@@ -508,14 +553,19 @@
};
doAnswer(unregisterStubAnswer).when(
mScreenMagnificationController).unregister(eq(TEST_DISPLAY));
- doAnswer(unregisterStubAnswer).when(
- mScreenMagnificationController).reset(eq(TEST_DISPLAY), anyBoolean());
}
public void resetAndStubMethods() {
Mockito.reset(mScreenMagnificationController);
stubMethods();
}
+
+ public void deactivateIfNeed() {
+ mScale = 1.0f;
+ mCenterX = 0;
+ mCenterY = 0;
+ mIsActivated = false;
+ }
}
private static class WindowMagnificationManagerStub {
@@ -523,6 +573,7 @@
private float mScale = 1.0f;
private float mCenterX = 0;
private float mCenterY = 0;
+ private boolean mIsEnabled = false;
WindowMagnificationManagerStub(
WindowMagnificationManager windowMagnificationManager) {
@@ -536,7 +587,11 @@
TEST_DISPLAY);
doAnswer(invocation -> mCenterY).when(mWindowMagnificationManager).getCenterY(
TEST_DISPLAY);
+ doAnswer(invocation -> mIsEnabled).when(mWindowMagnificationManager)
+ .isWindowMagnifierEnabled(TEST_DISPLAY);
+
Answer enableWindowMagnificationStubAnswer = invocation -> {
+ mIsEnabled = true;
mScale = invocation.getArgument(1);
mCenterX = invocation.getArgument(2);
mCenterY = invocation.getArgument(3);
@@ -548,11 +603,29 @@
doAnswer(enableWindowMagnificationStubAnswer).when(
mWindowMagnificationManager).enableWindowMagnification(eq(TEST_DISPLAY),
anyFloat(), anyFloat(), anyFloat(), any(), anyInt());
+
+ Answer disableWindowMagnificationStubAnswer = invocation -> {
+ deactivateIfNeed();
+ return true;
+ };
+ doAnswer(disableWindowMagnificationStubAnswer).when(
+ mWindowMagnificationManager).disableWindowMagnification(eq(TEST_DISPLAY),
+ anyBoolean());
+ doAnswer(disableWindowMagnificationStubAnswer).when(
+ mWindowMagnificationManager).disableWindowMagnification(eq(TEST_DISPLAY),
+ anyBoolean(), any());
}
public void resetAndStubMethods() {
Mockito.reset(mWindowMagnificationManager);
stubMethods();
}
+
+ public void deactivateIfNeed() {
+ mScale = 1.0f;
+ mCenterX = Float.NaN;
+ mCenterY = Float.NaN;
+ mIsEnabled = false;
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 231b2f32..407c575 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -26,6 +26,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -404,6 +405,22 @@
}
@Test
+ public void
+ configTransitionToWindowModeAndActivatedFalse_fullScreenMagnifying_doNotEnableWindow()
+ throws RemoteException {
+ activateMagnifier(MODE_FULLSCREEN, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y);
+
+ MagnificationConfig config = (new MagnificationConfig.Builder())
+ .setMode(MODE_WINDOW).setActivated(false).build();
+ mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY,
+ config, false, TEST_SERVICE_ID);
+
+ verify(mMockConnection.getConnection(), never()).enableWindowMagnification(anyInt(),
+ anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(),
+ nullable(IRemoteMagnificationAnimationCallback.class));
+ }
+
+ @Test
public void configTransitionToFullScreen_windowMagnifying_disableWindowAndEnableFullScreen()
throws RemoteException {
final boolean animate = true;
@@ -420,6 +437,49 @@
}
@Test
+ public void
+ configTransitionToFullScreenAndActivatedFalse_windowMagnifying_doNotEnableFullScreen()
+ throws RemoteException {
+ final boolean animate = true;
+ activateMagnifier(MODE_WINDOW, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y);
+ MagnificationConfig config = (new MagnificationConfig.Builder())
+ .setMode(MODE_FULLSCREEN).setActivated(false).build();
+ mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY,
+ config, animate, TEST_SERVICE_ID);
+ mMockConnection.invokeCallbacks();
+
+ verify(mScreenMagnificationController, never()).setScaleAndCenter(anyInt(),
+ anyFloat(), anyFloat(), anyFloat(),
+ anyBoolean(), anyInt());
+ }
+
+ @Test
+ public void configTransitionToActivatedFalse_fullScreenMagnifying_disableFullScreen()
+ throws RemoteException {
+ activateMagnifier(MODE_FULLSCREEN, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y);
+ MagnificationConfig config = (new MagnificationConfig.Builder())
+ .setMode(MODE_FULLSCREEN).setActivated(false).build();
+ mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY,
+ config, false, TEST_SERVICE_ID);
+
+ verify(mScreenMagnificationController).reset(eq(TEST_DISPLAY), eq(false));
+ }
+
+ @Test
+ public void configTransitionToActivatedFalse_windowMagnifying_disableWindow()
+ throws RemoteException {
+ final boolean animate = true;
+ activateMagnifier(MODE_WINDOW, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y);
+ MagnificationConfig config = (new MagnificationConfig.Builder())
+ .setMode(MODE_WINDOW).setActivated(false).build();
+ mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY,
+ config, animate, TEST_SERVICE_ID);
+
+ verify(mMockConnection.getConnection()).disableWindowMagnification(anyInt(),
+ nullable(IRemoteMagnificationAnimationCallback.class));
+ }
+
+ @Test
public void configTransitionToFullScreen_userSettingsDisablingFullScreen_enableFullScreen()
throws RemoteException {
activateMagnifier(MODE_FULLSCREEN, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y);
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 82f6493..c952609 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -945,13 +945,15 @@
}
mListener = listener;
- mListener.onSupportedDeviceStatesChanged(mSupportedDeviceStates);
+ mListener.onSupportedDeviceStatesChanged(mSupportedDeviceStates,
+ SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED);
mListener.onStateChanged(mSupportedDeviceStates[0].getIdentifier());
}
public void notifySupportedDeviceStates(DeviceState[] supportedDeviceStates) {
mSupportedDeviceStates = supportedDeviceStates;
- mListener.onSupportedDeviceStatesChanged(supportedDeviceStates);
+ mListener.onSupportedDeviceStatesChanged(supportedDeviceStates,
+ SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED);
}
public void setState(int identifier) {
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java
new file mode 100644
index 0000000..8196d6a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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.devicestate;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
+
+/**
+ * Unit tests for {@link DeviceStateNotificationController}.
+ * <p/>
+ * Run with <code>atest com.android.server.devicestate.DeviceStateNotificationControllerTest</code>.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class DeviceStateNotificationControllerTest {
+
+ private static final int STATE_WITHOUT_NOTIFICATION = 1;
+ private static final int STATE_WITH_ACTIVE_NOTIFICATION = 2;
+ private static final int STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION = 3;
+
+ private static final int VALID_APP_UID = 1000;
+ private static final int INVALID_APP_UID = 2000;
+ private static final String VALID_APP_NAME = "Valid app name";
+ private static final String INVALID_APP_NAME = "Invalid app name";
+ private static final String VALID_APP_LABEL = "Valid app label";
+
+ private static final String NAME_1 = "name1";
+ private static final String TITLE_1 = "title1";
+ private static final String CONTENT_1 = "content1:%1$s";
+ private static final String NAME_2 = "name2";
+ private static final String TITLE_2 = "title2";
+ private static final String CONTENT_2 = "content2:%1$s";
+ private static final String THERMAL_TITLE_2 = "thermal_title2";
+ private static final String THERMAL_CONTENT_2 = "thermal_content2";
+
+ private DeviceStateNotificationController mController;
+
+ private final ArgumentCaptor<Notification> mNotificationCaptor = ArgumentCaptor.forClass(
+ Notification.class);
+ private final NotificationManager mNotificationManager = mock(NotificationManager.class);
+
+ @Before
+ public void setup() throws Exception {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ Handler handler = mock(Handler.class);
+ PackageManager packageManager = mock(PackageManager.class);
+ Runnable cancelStateRunnable = mock(Runnable.class);
+ ApplicationInfo applicationInfo = mock(ApplicationInfo.class);
+
+ final SparseArray<DeviceStateNotificationController.NotificationInfo> notificationInfos =
+ new SparseArray<>();
+ notificationInfos.put(STATE_WITH_ACTIVE_NOTIFICATION,
+ new DeviceStateNotificationController.NotificationInfo(
+ NAME_1, TITLE_1, CONTENT_1,
+ "", ""));
+ notificationInfos.put(STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION,
+ new DeviceStateNotificationController.NotificationInfo(
+ NAME_2, TITLE_2, CONTENT_2,
+ THERMAL_TITLE_2, THERMAL_CONTENT_2));
+
+ when(packageManager.getNameForUid(VALID_APP_UID)).thenReturn(VALID_APP_NAME);
+ when(packageManager.getNameForUid(INVALID_APP_UID)).thenReturn(INVALID_APP_NAME);
+ when(packageManager.getApplicationInfo(eq(VALID_APP_NAME), ArgumentMatchers.any()))
+ .thenReturn(applicationInfo);
+ when(packageManager.getApplicationInfo(eq(INVALID_APP_NAME), ArgumentMatchers.any()))
+ .thenThrow(new PackageManager.NameNotFoundException());
+ when(applicationInfo.loadLabel(eq(packageManager))).thenReturn(VALID_APP_LABEL);
+
+ mController = new DeviceStateNotificationController(
+ context, handler, cancelStateRunnable, notificationInfos,
+ packageManager, mNotificationManager);
+ }
+
+ @Test
+ public void test_activeNotification() {
+ mController.showStateActiveNotificationIfNeeded(
+ STATE_WITH_ACTIVE_NOTIFICATION, VALID_APP_UID);
+
+ // Verify that the notification manager is called with correct notification information.
+ verify(mNotificationManager).notify(
+ eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+ eq(DeviceStateNotificationController.NOTIFICATION_ID),
+ mNotificationCaptor.capture());
+ Notification notification = mNotificationCaptor.getValue();
+ assertEquals(TITLE_1, notification.extras.getString(Notification.EXTRA_TITLE));
+ assertEquals(String.format(CONTENT_1, VALID_APP_LABEL),
+ notification.extras.getString(Notification.EXTRA_TEXT));
+ assertEquals(Notification.FLAG_ONGOING_EVENT,
+ notification.flags & Notification.FLAG_ONGOING_EVENT);
+
+ // Verify that the notification action is as expected.
+ Notification.Action[] actions = notification.actions;
+ assertEquals(1, actions.length);
+ Notification.Action action = actions[0];
+ assertEquals(DeviceStateNotificationController.INTENT_ACTION_CANCEL_STATE,
+ action.actionIntent.getIntent().getAction());
+
+ // Verify that the notification is properly canceled.
+ mController.cancelNotification(STATE_WITH_ACTIVE_NOTIFICATION);
+ verify(mNotificationManager).cancel(
+ DeviceStateNotificationController.NOTIFICATION_TAG,
+ DeviceStateNotificationController.NOTIFICATION_ID);
+ }
+
+ @Test
+ public void test_thermalNotification() {
+ // Verify that the active notification is created.
+ mController.showStateActiveNotificationIfNeeded(
+ STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION, VALID_APP_UID);
+ verify(mNotificationManager).notify(
+ eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+ eq(DeviceStateNotificationController.NOTIFICATION_ID),
+ mNotificationCaptor.capture());
+ Notification notification = mNotificationCaptor.getValue();
+ assertEquals(TITLE_2, notification.extras.getString(Notification.EXTRA_TITLE));
+ assertEquals(String.format(CONTENT_2, VALID_APP_LABEL),
+ notification.extras.getString(Notification.EXTRA_TEXT));
+ assertEquals(Notification.FLAG_ONGOING_EVENT,
+ notification.flags & Notification.FLAG_ONGOING_EVENT);
+ Mockito.clearInvocations(mNotificationManager);
+
+ // Verify that the thermal critical notification is created.
+ mController.showThermalCriticalNotificationIfNeeded(
+ STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION);
+ verify(mNotificationManager).notify(
+ eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+ eq(DeviceStateNotificationController.NOTIFICATION_ID),
+ mNotificationCaptor.capture());
+ notification = mNotificationCaptor.getValue();
+ assertEquals(THERMAL_TITLE_2, notification.extras.getString(Notification.EXTRA_TITLE));
+ assertEquals(THERMAL_CONTENT_2, notification.extras.getString(Notification.EXTRA_TEXT));
+ assertEquals(0, notification.flags & Notification.FLAG_ONGOING_EVENT);
+
+ // Verify that the notification is canceled.
+ mController.cancelNotification(STATE_WITH_ACTIVE_NOTIFICATION);
+ verify(mNotificationManager).cancel(
+ DeviceStateNotificationController.NOTIFICATION_TAG,
+ DeviceStateNotificationController.NOTIFICATION_ID);
+ }
+
+ @Test
+ public void test_deviceStateWithoutNotification() {
+ // Verify that no notification is created.
+ mController.showStateActiveNotificationIfNeeded(
+ STATE_WITHOUT_NOTIFICATION, VALID_APP_UID);
+ verify(mNotificationManager, Mockito.never()).notify(
+ eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+ eq(DeviceStateNotificationController.NOTIFICATION_ID),
+ mNotificationCaptor.capture());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
index 430504c..fe37f42 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
@@ -58,7 +58,7 @@
@Test
public void addRequest() {
- OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
assertNull(mStatusListener.getLastStatus(request));
@@ -68,14 +68,14 @@
@Test
public void addRequest_cancelExistingRequestThroughNewRequest() {
- OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
assertNull(mStatusListener.getLastStatus(firstRequest));
mController.addRequest(firstRequest);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
- OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
assertNull(mStatusListener.getLastStatus(secondRequest));
@@ -86,7 +86,7 @@
@Test
public void addRequest_cancelActiveRequest() {
- OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
mController.addRequest(firstRequest);
@@ -100,7 +100,7 @@
@Test
public void addBaseStateRequest() {
- OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
assertNull(mStatusListener.getLastStatus(request));
@@ -110,14 +110,14 @@
@Test
public void addBaseStateRequest_cancelExistingBaseStateRequestThroughNewRequest() {
- OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
assertNull(mStatusListener.getLastStatus(firstRequest));
mController.addBaseStateRequest(firstRequest);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
- OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
assertNull(mStatusListener.getLastStatus(secondRequest));
@@ -128,7 +128,7 @@
@Test
public void addBaseStateRequest_cancelActiveBaseStateRequest() {
- OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
mController.addBaseStateRequest(firstRequest);
@@ -142,12 +142,13 @@
@Test
public void handleBaseStateChanged() {
- OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */,
DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */,
OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* uid */,
0 /* requestedState */,
0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
@@ -167,10 +168,11 @@
@Test
public void handleProcessDied() {
- OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* uid */,
1 /* requestedState */,
0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
@@ -189,10 +191,11 @@
public void handleProcessDied_stickyRequests() {
mController.setStickyRequestsAllowed(true);
- OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* uid */,
1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
mController.addRequest(firstRequest);
@@ -211,10 +214,11 @@
@Test
public void handleNewSupportedStates() {
- OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* uid */,
1 /* requestedState */,
0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
@@ -224,18 +228,20 @@
mController.addBaseStateRequest(baseStateRequest);
assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
- mController.handleNewSupportedStates(new int[]{0, 1});
+ mController.handleNewSupportedStates(new int[]{0, 1},
+ DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
- mController.handleNewSupportedStates(new int[]{0});
+ mController.handleNewSupportedStates(new int[]{0},
+ DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED);
}
@Test
public void cancelOverrideRequestsTest() {
- OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
mController.addRequest(firstRequest);
@@ -250,7 +256,7 @@
private Map<OverrideRequest, Integer> mLastStatusMap = new HashMap<>();
@Override
- public void onStatusChanged(@NonNull OverrideRequest request, int newStatus) {
+ public void onStatusChanged(@NonNull OverrideRequest request, int newStatus, int flags) {
mLastStatusMap.put(request, newStatus);
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
index bcae50e..a192913 100644
--- a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -64,10 +64,12 @@
Layout testLayout = new Layout();
testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(123456L), /* isDefault= */ true,
- /* isEnabled= */ true, mDisplayIdProducerMock);
+ /* isEnabled= */ true, mDisplayIdProducerMock,
+ /* brightnessThrottlingMapId= */ null);
testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(78910L), /* isDefault= */ false,
- /* isEnabled= */ false, mDisplayIdProducerMock);
+ /* isEnabled= */ false, mDisplayIdProducerMock,
+ /* brightnessThrottlingMapId= */ null);
assertEquals(testLayout, configLayout);
}
@@ -78,10 +80,12 @@
Layout testLayout = new Layout();
testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(78910L), /* isDefault= */ true,
- /* isEnabled= */ true, mDisplayIdProducerMock);
+ /* isEnabled= */ true, mDisplayIdProducerMock,
+ /* brightnessThrottlingMapId= */ null);
testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(123456L), /* isDefault= */ false,
- /* isEnabled= */ false, mDisplayIdProducerMock);
+ /* isEnabled= */ false, mDisplayIdProducerMock,
+ /* brightnessThrottlingMapId= */ null);
assertEquals(testLayout, configLayout);
}
@@ -91,16 +95,30 @@
Layout configLayout = mDeviceStateToLayoutMap.get(2);
Layout testLayout = new Layout();
- testLayout.createDisplayLocked(
+
+ Layout.Display display1 = testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(345L), /* isDefault= */ true,
- /* isEnabled= */ true, mDisplayIdProducerMock);
- testLayout.createDisplayLocked(
+ /* isEnabled= */ true, mDisplayIdProducerMock,
+ /* brightnessThrottlingMapId= */ "concurrent");
+ display1.setPosition(Layout.Display.POSITION_FRONT);
+
+ Layout.Display display2 = testLayout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(678L), /* isDefault= */ false,
- /* isEnabled= */ true, mDisplayIdProducerMock);
+ /* isEnabled= */ true, mDisplayIdProducerMock,
+ /* brightnessThrottlingMapId= */ "concurrent");
+ display2.setPosition(Layout.Display.POSITION_REAR);
assertEquals(testLayout, configLayout);
}
+ @Test
+ public void testRearDisplayLayout() {
+ Layout configLayout = mDeviceStateToLayoutMap.get(2);
+
+ assertEquals(Layout.Display.POSITION_FRONT, configLayout.getAt(0).getPosition());
+ assertEquals(Layout.Display.POSITION_REAR, configLayout.getAt(1).getPosition());
+ }
+
////////////////////
// Helper Methods //
////////////////////
@@ -139,9 +157,13 @@
+ "<state>2</state> \n"
+ "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+ "<address>345</address>\n"
+ + "<position>front</position>\n"
+ + "<brightnessThrottlingMapId>concurrent</brightnessThrottlingMapId>\n"
+ "</display>\n"
+ "<display enabled=\"true\">\n"
+ "<address>678</address>\n"
+ + "<position>rear</position>\n"
+ + "<brightnessThrottlingMapId>concurrent</brightnessThrottlingMapId>\n"
+ "</display>\n"
+ "</layout>\n"
+ "</layouts>\n";
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 0ea20a8..42bdbec 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -53,6 +53,8 @@
public final class DisplayDeviceConfigTest {
private static final int DEFAULT_PEAK_REFRESH_RATE = 75;
private static final int DEFAULT_REFRESH_RATE = 120;
+ private static final int DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE = 55;
+ private static final int DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE = 95;
private static final int[] LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{10, 30};
private static final int[] LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{1, 21};
private static final int[] HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{160};
@@ -149,8 +151,10 @@
assertArrayEquals(new float[]{23, 24, 25},
mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
- assertEquals(75, mDisplayDeviceConfig.getDefaultLowRefreshRate());
- assertEquals(90, mDisplayDeviceConfig.getDefaultHighRefreshRate());
+ assertEquals(75, mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate());
+ assertEquals(90, mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate());
+ assertEquals(85, mDisplayDeviceConfig.getDefaultPeakRefreshRate());
+ assertEquals(45, mDisplayDeviceConfig.getDefaultRefreshRate());
assertArrayEquals(new int[]{45, 55},
mDisplayDeviceConfig.getLowDisplayBrightnessThresholds());
assertArrayEquals(new int[]{50, 60},
@@ -189,7 +193,7 @@
DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.025f
));
assertEquals(new DisplayDeviceConfig.BrightnessThrottlingData(throttlingLevels),
- mDisplayDeviceConfig.getBrightnessThrottlingData());
+ mDisplayDeviceConfig.getBrightnessThrottlingData("default"));
throttlingLevels.clear();
throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel(
@@ -211,7 +215,7 @@
DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.0125f
));
assertEquals(new DisplayDeviceConfig.BrightnessThrottlingData(throttlingLevels),
- mDisplayDeviceConfig.getConcurrentDisplaysBrightnessThrottlingData());
+ mDisplayDeviceConfig.getBrightnessThrottlingData("concurrent"));
assertNotNull(mDisplayDeviceConfig.getHostUsiVersion());
assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMajorVersion(), 2);
@@ -278,8 +282,12 @@
mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
assertArrayEquals(new float[]{29, 30, 31},
mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
- assertEquals(mDisplayDeviceConfig.getDefaultLowRefreshRate(), DEFAULT_REFRESH_RATE);
- assertEquals(mDisplayDeviceConfig.getDefaultHighRefreshRate(), DEFAULT_PEAK_REFRESH_RATE);
+ assertEquals(mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate(),
+ DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE);
+ assertEquals(mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate(),
+ DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE);
+ assertEquals(mDisplayDeviceConfig.getDefaultPeakRefreshRate(), DEFAULT_PEAK_REFRESH_RATE);
+ assertEquals(mDisplayDeviceConfig.getDefaultRefreshRate(), DEFAULT_REFRESH_RATE);
assertArrayEquals(mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(),
LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
assertArrayEquals(mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(),
@@ -509,7 +517,7 @@
+ "<brightness>0.025</brightness>\n"
+ "</brightnessThrottlingPoint>\n"
+ "</brightnessThrottlingMap>\n"
- + "<concurrentDisplaysBrightnessThrottlingMap>\n"
+ + "<brightnessThrottlingMap id=\"concurrent\">\n"
+ "<brightnessThrottlingPoint>\n"
+ "<thermalStatus>light</thermalStatus>\n"
+ "<brightness>0.2</brightness>\n"
@@ -534,9 +542,11 @@
+ "<thermalStatus>shutdown</thermalStatus>\n"
+ "<brightness>0.0125</brightness>\n"
+ "</brightnessThrottlingPoint>\n"
- + "</concurrentDisplaysBrightnessThrottlingMap>\n"
+ + "</brightnessThrottlingMap>\n"
+ "</thermalThrottling>\n"
+ "<refreshRate>\n"
+ + "<defaultRefreshRate>45</defaultRefreshRate>\n"
+ + "<defaultPeakRefreshRate>85</defaultPeakRefreshRate>\n"
+ "<lowerBlockingZoneConfigs>\n"
+ "<defaultRefreshRate>75</defaultRefreshRate>\n"
+ "<blockingZoneThreshold>\n"
@@ -642,10 +652,14 @@
.thenReturn(new int[]{370, 380, 390});
// Configs related to refresh rates and blocking zones
- when(mResources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate))
+ when(mResources.getInteger(R.integer.config_defaultPeakRefreshRate))
.thenReturn(DEFAULT_PEAK_REFRESH_RATE);
- when(mResources.getInteger(com.android.internal.R.integer.config_defaultRefreshRate))
+ when(mResources.getInteger(R.integer.config_defaultRefreshRate))
.thenReturn(DEFAULT_REFRESH_RATE);
+ when(mResources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
+ .thenReturn(DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE);
+ when(mResources.getInteger(R.integer.config_defaultRefreshRateInZone))
+ .thenReturn(DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE);
when(mResources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
.thenReturn(LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
when(mResources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 2d252cb..1904671 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -56,6 +56,7 @@
import android.hardware.display.DisplayViewport;
import android.hardware.display.DisplayedContentSample;
import android.hardware.display.DisplayedContentSamplingAttributes;
+import android.hardware.display.HdrConversionMode;
import android.hardware.display.IDisplayManagerCallback;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
@@ -157,7 +158,7 @@
VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context,
Handler handler, DisplayAdapter.Listener displayAdapterListener) {
return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
- (String name, boolean secure) -> mMockDisplayToken);
+ (String name, boolean secure, float refreshRate) -> mMockDisplayToken);
}
@Override
@@ -171,6 +172,17 @@
}
});
}
+
+ @Override
+ void setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
+ int[] autoHdrTypes) {
+ return;
+ }
+
+ @Override
+ int[] getSupportedHdrOutputTypes() {
+ return new int[]{};
+ }
}
private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
@@ -240,8 +252,7 @@
// the usage of SensorManager, which is available only after the PowerManagerService
// is ready.
resetConfigToIgnoreSensorManager(mContext);
- DisplayManagerService displayManager =
- new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
registerDefaultDisplays(displayManager);
displayManager.systemReady(false /* safeMode */);
displayManager.windowManagerAndInputReady();
@@ -316,8 +327,7 @@
// the usage of SensorManager, which is available only after the PowerManagerService
// is ready.
resetConfigToIgnoreSensorManager(mContext);
- DisplayManagerService displayManager =
- new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
registerDefaultDisplays(displayManager);
displayManager.systemReady(false /* safeMode */);
displayManager.windowManagerAndInputReady();
@@ -1511,6 +1521,22 @@
}
+ @Test
+ public void testHdrConversionModeEquals() {
+ assertEquals(
+ new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2),
+ new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2));
+ assertNotEquals(
+ new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2),
+ new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 3));
+ assertEquals(
+ new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM),
+ new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+ assertNotEquals(
+ new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2),
+ new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+ }
+
private void testDisplayInfoFrameRateOverrideModeCompat(boolean compatChangeEnabled)
throws Exception {
DisplayManagerService displayManager =
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index a35f4ea..27d912b 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -2260,6 +2260,10 @@
.thenReturn(75);
when(resources.getInteger(R.integer.config_defaultRefreshRate))
.thenReturn(45);
+ when(resources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
+ .thenReturn(65);
+ when(resources.getInteger(R.integer.config_defaultRefreshRateInZone))
+ .thenReturn(85);
when(resources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
.thenReturn(new int[]{5});
when(resources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
@@ -2279,6 +2283,8 @@
assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 45, 0.0);
assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 75,
0.0);
+ assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 65);
+ assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 85);
assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
new int[]{250});
assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
@@ -2290,17 +2296,21 @@
// Notify that the default display is updated, such that DisplayDeviceConfig has new values
DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class);
- when(displayDeviceConfig.getDefaultLowRefreshRate()).thenReturn(50);
- when(displayDeviceConfig.getDefaultHighRefreshRate()).thenReturn(55);
+ when(displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate()).thenReturn(50);
+ when(displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate()).thenReturn(55);
+ when(displayDeviceConfig.getDefaultRefreshRate()).thenReturn(60);
+ when(displayDeviceConfig.getDefaultPeakRefreshRate()).thenReturn(65);
when(displayDeviceConfig.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25});
when(displayDeviceConfig.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
when(displayDeviceConfig.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
when(displayDeviceConfig.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{2100});
director.defaultDisplayDeviceUpdated(displayDeviceConfig);
- assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 50, 0.0);
- assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 55,
+ assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0);
+ assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 65,
0.0);
+ assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 55);
+ assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 50);
assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
new int[]{210});
assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
@@ -2313,6 +2323,8 @@
// Notify that the default display is updated, such that DeviceConfig has new values
FakeDeviceConfig config = mInjector.getDeviceConfig();
config.setDefaultPeakRefreshRate(60);
+ config.setRefreshRateInHighZone(65);
+ config.setRefreshRateInLowZone(70);
config.setLowAmbientBrightnessThresholds(new int[]{20});
config.setLowDisplayBrightnessThresholds(new int[]{10});
config.setHighDisplayBrightnessThresholds(new int[]{255});
@@ -2320,9 +2332,11 @@
director.defaultDisplayDeviceUpdated(displayDeviceConfig);
- assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 50, 0.0);
+ assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0);
assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 60,
0.0);
+ assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 65);
+ assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 70);
assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
new int[]{255});
assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
@@ -2362,8 +2376,8 @@
any(Handler.class));
DisplayDeviceConfig ddcMock = mock(DisplayDeviceConfig.class);
- when(ddcMock.getDefaultLowRefreshRate()).thenReturn(50);
- when(ddcMock.getDefaultHighRefreshRate()).thenReturn(55);
+ when(ddcMock.getDefaultLowBlockingZoneRefreshRate()).thenReturn(50);
+ when(ddcMock.getDefaultHighBlockingZoneRefreshRate()).thenReturn(55);
when(ddcMock.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25});
when(ddcMock.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
when(ddcMock.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index a860387..8a37ed9 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -19,6 +19,7 @@
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.DEFAULT_DISPLAY_GROUP;
+import static android.view.Display.FLAG_REAR;
import static android.view.Display.TYPE_EXTERNAL;
import static android.view.Display.TYPE_INTERNAL;
import static android.view.Display.TYPE_VIRTUAL;
@@ -30,6 +31,8 @@
import static com.android.server.display.DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
+import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
+import static com.android.server.display.layout.Layout.Display.POSITION_UNKNOWN;
import static com.google.common.truth.Truth.assertThat;
@@ -296,9 +299,9 @@
Layout layout1 = new Layout();
layout1.createDisplayLocked(info(device1).address, /* isDefault= */ true,
- /* isEnabled= */ true, mIdProducer);
+ /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null);
layout1.createDisplayLocked(info(device2).address, /* isDefault= */ false,
- /* isEnabled= */ true, mIdProducer);
+ /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null);
when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
assertThat(layout1.size()).isEqualTo(2);
final int logicalId2 = layout1.getByAddress(info(device2).address).getLogicalDisplayId();
@@ -331,17 +334,17 @@
add(device3);
Layout layout1 = new Layout();
- layout1.createDisplayLocked(info(device1).address,
- /* isDefault= */ true, /* isEnabled= */ true, mIdProducer);
+ layout1.createDisplayLocked(info(device1).address, /* isDefault= */ true,
+ /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null);
when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
final int layoutState2 = 2;
Layout layout2 = new Layout();
- layout2.createDisplayLocked(info(device2).address,
- /* isDefault= */ false, /* isEnabled= */ true, mIdProducer);
+ layout2.createDisplayLocked(info(device2).address, /* isDefault= */ false,
+ /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null);
// Device3 is the default display.
- layout2.createDisplayLocked(info(device3).address,
- /* isDefault= */ true, /* isEnabled= */ true, mIdProducer);
+ layout2.createDisplayLocked(info(device3).address, /* isDefault= */ true,
+ /* isEnabled= */ true, mIdProducer, /* brightnessThrottlingMapId= */ null);
when(mDeviceStateToLayoutMapSpy.get(layoutState2)).thenReturn(layout2);
assertThat(layout2.size()).isEqualTo(2);
final int logicalId2 = layout2.getByAddress(info(device2).address).getLogicalDisplayId();
@@ -563,16 +566,18 @@
Layout layout = new Layout();
layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
- true, true, mIdProducer);
+ true, true, mIdProducer,
+ /* brightnessThrottlingMapId= */ "concurrent");
layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
- false, true, mIdProducer);
+ false, true, mIdProducer,
+ /* brightnessThrottlingMapId= */ "concurrent");
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
layout = new Layout();
layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
- false, false, mIdProducer);
+ false, false, mIdProducer, /* brightnessThrottlingMapId= */ null);
layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
- true, true, mIdProducer);
+ true, true, mIdProducer, /* brightnessThrottlingMapId= */ null);
when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(layout);
when(mDeviceStateToLayoutMapSpy.get(2)).thenReturn(layout);
@@ -599,6 +604,10 @@
assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
+ assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device1)
+ .getBrightnessThrottlingDataIdLocked());
+ assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device2)
+ .getBrightnessThrottlingDataIdLocked());
mLogicalDisplayMapper.setDeviceStateLocked(1, false);
advanceTime(1000);
@@ -606,6 +615,12 @@
assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
+ assertEquals(DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID,
+ mLogicalDisplayMapper.getDisplayLocked(device1)
+ .getBrightnessThrottlingDataIdLocked());
+ assertEquals(DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID,
+ mLogicalDisplayMapper.getDisplayLocked(device2)
+ .getBrightnessThrottlingDataIdLocked());
mLogicalDisplayMapper.setDeviceStateLocked(2, false);
advanceTime(1000);
@@ -613,6 +628,12 @@
assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
+ assertEquals(DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID,
+ mLogicalDisplayMapper.getDisplayLocked(device1)
+ .getBrightnessThrottlingDataIdLocked());
+ assertEquals(DisplayDeviceConfig.DEFAULT_BRIGHTNESS_THROTTLING_DATA_ID,
+ mLogicalDisplayMapper.getDisplayLocked(device2)
+ .getBrightnessThrottlingDataIdLocked());
}
@Test
@@ -633,17 +654,20 @@
displayAddressOne,
/* isDefault= */ true,
/* isEnabled= */ true,
- mIdProducer);
+ mIdProducer,
+ /* brightnessThrottlingMapId= */ null);
threeDevicesEnabledLayout.createDisplayLocked(
displayAddressTwo,
/* isDefault= */ false,
/* isEnabled= */ true,
- mIdProducer);
+ mIdProducer,
+ /* brightnessThrottlingMapId= */ null);
threeDevicesEnabledLayout.createDisplayLocked(
displayAddressThree,
/* isDefault= */ false,
/* isEnabled= */ true,
- mIdProducer);
+ mIdProducer,
+ /* brightnessThrottlingMapId= */ null);
when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT))
.thenReturn(threeDevicesEnabledLayout);
@@ -678,17 +702,20 @@
displayAddressOne,
/* isDefault= */ true,
/* isEnabled= */ true,
- mIdProducer);
+ mIdProducer,
+ /* brightnessThrottlingMapId= */ null);
oneDeviceEnabledLayout.createDisplayLocked(
displayAddressTwo,
/* isDefault= */ false,
/* isEnabled= */ false,
- mIdProducer);
+ mIdProducer,
+ /* brightnessThrottlingMapId= */ null);
oneDeviceEnabledLayout.createDisplayLocked(
displayAddressThree,
/* isDefault= */ false,
/* isEnabled= */ false,
- mIdProducer);
+ mIdProducer,
+ /* brightnessThrottlingMapId= */ null);
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(oneDeviceEnabledLayout);
when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(threeDevicesEnabledLayout);
@@ -754,6 +781,45 @@
assertFalse(display2.isEnabledLocked());
}
+ @Test
+ public void testDisplayFlagRear() {
+ DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_REAR);
+
+ Layout layout = new Layout();
+ layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
+ true, true, mIdProducer, /* brightnessThrottlingMapId= */ null);
+ layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
+ false, true, mIdProducer, /* brightnessThrottlingMapId= */ null,
+ POSITION_REAR);
+ when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
+
+ when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1);
+
+ LogicalDisplay display1 = add(device1);
+ assertEquals(info(display1).address, info(device1).address);
+ assertEquals(DEFAULT_DISPLAY, id(display1));
+
+ LogicalDisplay display2 = add(device2);
+ assertEquals(info(display2).address, info(device2).address);
+ // We can only have one default display
+ assertEquals(DEFAULT_DISPLAY, id(display1));
+
+ mLogicalDisplayMapper.setDeviceStateLocked(0, false);
+ advanceTime(1000);
+ mLogicalDisplayMapper.onBootCompleted();
+ advanceTime(1000);
+
+ assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
+ assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
+
+ assertEquals(POSITION_UNKNOWN,
+ mLogicalDisplayMapper.getDisplayLocked(device1).getPositionLocked());
+ assertEquals(POSITION_REAR,
+ mLogicalDisplayMapper.getDisplayLocked(device2).getPositionLocked());
+ }
/////////////////
// Helper Methods
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
new file mode 100644
index 0000000..52c6777
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.BugreportManager.BugreportCallback;
+import android.os.IBinder;
+import android.os.IDumpstateListener;
+import android.os.RemoteException;
+import android.util.ArraySet;
+import android.util.Pair;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileDescriptor;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class BugreportManagerServiceImplTest {
+
+ Context mContext;
+ BugreportManagerServiceImpl mService;
+ BugreportManagerServiceImpl.BugreportFileManager mBugreportFileManager;
+
+ int mCallingUid = 1234;
+ String mCallingPackage = "test.package";
+
+ String mBugreportFile = "bugreport-file.zip";
+ String mBugreportFile2 = "bugreport-file2.zip";
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ ArraySet<String> mAllowlistedPackages = new ArraySet<>();
+ mAllowlistedPackages.add(mContext.getPackageName());
+ mService = new BugreportManagerServiceImpl(
+ new BugreportManagerServiceImpl.Injector(mContext, mAllowlistedPackages));
+ mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager();
+ }
+
+ @Test
+ public void testBugreportFileManagerFileExists() {
+ Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
+ mBugreportFileManager.addBugreportFileForCaller(
+ callingInfo, mBugreportFile);
+
+ assertThrows(IllegalArgumentException.class, () ->
+ mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
+ callingInfo, "unknown-file.zip"));
+
+ // No exception should be thrown.
+ mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile);
+ }
+
+ @Test
+ public void testBugreportFileManagerMultipleFiles() {
+ Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
+ mBugreportFileManager.addBugreportFileForCaller(
+ callingInfo, mBugreportFile);
+ mBugreportFileManager.addBugreportFileForCaller(
+ callingInfo, mBugreportFile2);
+
+ // No exception should be thrown.
+ mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile);
+ mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile2);
+ }
+
+ @Test
+ public void testBugreportFileManagerFileDoesNotExist() {
+ Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
+ assertThrows(IllegalArgumentException.class,
+ () -> mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
+ callingInfo, "test-file.zip"));
+ }
+
+ @Test
+ public void testRetrieveBugreportWithoutFilesForCaller() throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ Listener listener = new Listener(latch);
+ mService.retrieveBugreport(Binder.getCallingUid(), mContext.getPackageName(),
+ new FileDescriptor(), mBugreportFile, listener);
+ assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(listener.getErrorCode()).isEqualTo(
+ BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
+ }
+
+ private static class Listener implements IDumpstateListener {
+ CountDownLatch mLatch;
+ int mErrorCode;
+
+ Listener(CountDownLatch latch) {
+ mLatch = latch;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+
+ @Override
+ public void onProgress(int progress) throws RemoteException {
+ }
+
+ @Override
+ public void onError(int errorCode) throws RemoteException {
+ mErrorCode = errorCode;
+ mLatch.countDown();
+ }
+
+ @Override
+ public void onFinished(String bugreportFile) throws RemoteException {
+ mLatch.countDown();
+ }
+
+ @Override
+ public void onScreenshotTaken(boolean success) throws RemoteException {
+ }
+
+ @Override
+ public void onUiIntensiveBugreportDumpsFinished() throws RemoteException {
+ }
+
+ int getErrorCode() {
+ return mErrorCode;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/os/OWNERS b/services/tests/servicestests/src/com/android/server/os/OWNERS
new file mode 100644
index 0000000..feb8011
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/os/OWNERS
@@ -0,0 +1,2 @@
+# Component 153446
+include /platform/frameworks/native:/cmds/dumpstate/OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
new file mode 100644
index 0000000..9902446
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "BugreportManagerServiceImplTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.os."
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 7fac9b6..7125796 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -19,6 +19,9 @@
import static android.content.Context.SENSOR_SERVICE;
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED;
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL;
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL;
import static com.android.server.policy.DeviceStateProviderImpl.DEFAULT_DEVICE_STATE;
import static org.junit.Assert.assertArrayEquals;
@@ -124,7 +127,8 @@
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
- verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
assertArrayEquals(new DeviceState[]{DEFAULT_DEVICE_STATE},
mDeviceStateArrayCaptor.getValue());
@@ -151,7 +155,8 @@
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
- verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
final DeviceState[] expectedStates = new DeviceState[]{
new DeviceState(1, "", 0 /* flags */),
new DeviceState(2, "", 0 /* flags */) };
@@ -183,7 +188,8 @@
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
- verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
final DeviceState[] expectedStates = new DeviceState[]{
new DeviceState(1, "", DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS),
new DeviceState(2, "", 0 /* flags */) };
@@ -212,7 +218,8 @@
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
- verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
final DeviceState[] expectedStates = new DeviceState[]{
new DeviceState(1, "", 0 /* flags */),
new DeviceState(2, "", 0 /* flags */) };
@@ -247,7 +254,8 @@
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
- verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
final DeviceState[] expectedStates = new DeviceState[]{
new DeviceState(1, "", 0 /* flags */),
new DeviceState(2, "CLOSED", 0 /* flags */) };
@@ -265,7 +273,8 @@
Mockito.clearInvocations(listener);
provider.notifyLidSwitchChanged(1, true /* lidOpen */);
- verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(1, mIntegerCaptor.getValue().intValue());
}
@@ -336,7 +345,8 @@
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
- verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
assertArrayEquals(
new DeviceState[]{
new DeviceState(1, "CLOSED", 0 /* flags */),
@@ -358,7 +368,8 @@
provider.onSensorChanged(event0);
- verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(3, mIntegerCaptor.getValue().intValue());
@@ -370,7 +381,8 @@
provider.onSensorChanged(event1);
- verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(2, mIntegerCaptor.getValue().intValue());
@@ -382,7 +394,8 @@
provider.onSensorChanged(event2);
- verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(1, mIntegerCaptor.getValue().intValue());
}
@@ -397,7 +410,8 @@
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
- verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
assertArrayEquals(
new DeviceState[]{
new DeviceState(1, "CLOSED", 0 /* flags */),
@@ -410,12 +424,14 @@
Mockito.clearInvocations(listener);
provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_MODERATE);
- verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
Mockito.clearInvocations(listener);
// The THERMAL_TEST state should be disabled.
provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_CRITICAL);
- verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL));
assertArrayEquals(
new DeviceState[]{
new DeviceState(1, "CLOSED", 0 /* flags */),
@@ -426,7 +442,8 @@
// The THERMAL_TEST state should be re-enabled.
provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_LIGHT);
- verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL));
assertArrayEquals(
new DeviceState[]{
new DeviceState(1, "CLOSED", 0 /* flags */),
@@ -468,7 +485,8 @@
provider.onSensorChanged(event2);
- verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
verify(listener, never()).onStateChanged(mIntegerCaptor.capture());
}
@@ -513,7 +531,8 @@
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
- verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+ eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
assertArrayEquals(
new DeviceState[]{
new DeviceState(1, "CLOSED", 0 /* flags */),
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index fc830a9..9facb4b 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -516,7 +516,7 @@
long vibrationId = 1;
VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
- Vibration vibration = createVibration(vibrationId, CombinedVibration.createParallel(
+ HalVibration vibration = createVibration(vibrationId, CombinedVibration.createParallel(
VibrationEffect.get(VibrationEffect.EFFECT_CLICK)));
vibration.addFallback(VibrationEffect.EFFECT_CLICK, fallback);
startThreadAndDispatcher(vibration);
@@ -683,7 +683,7 @@
.addEffect(VibrationEffect.get(VibrationEffect.EFFECT_TICK))
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
.compose();
- Vibration vib = createVibration(vibrationId, CombinedVibration.createParallel(effect));
+ HalVibration vib = createVibration(vibrationId, CombinedVibration.createParallel(effect));
vib.addFallback(VibrationEffect.EFFECT_TICK, fallback);
startThreadAndDispatcher(vib);
waitForCompletion();
@@ -1592,7 +1592,7 @@
return startThreadAndDispatcher(createVibration(vibrationId, effect));
}
- private VibrationStepConductor startThreadAndDispatcher(Vibration vib) {
+ private VibrationStepConductor startThreadAndDispatcher(HalVibration vib) {
mControllers = createVibratorControllers();
VibrationStepConductor conductor = new VibrationStepConductor(vib, mVibrationSettings,
mEffectAdapter, mControllers, mManagerHooks);
@@ -1624,8 +1624,8 @@
mTestLooper.dispatchAll(); // Flush callbacks
}
- private Vibration createVibration(long id, CombinedVibration effect) {
- return new Vibration(mVibrationToken, (int) id, effect, ATTRS, UID, DISPLAY_ID,
+ private HalVibration createVibration(long id, CombinedVibration effect) {
+ return new HalVibration(mVibrationToken, (int) id, effect, ATTRS, UID, DISPLAY_ID,
PACKAGE_NAME, "reason");
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index c83afb7..f040da8 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -2034,7 +2034,7 @@
private void vibrateAndWaitUntilFinished(VibratorManagerService service,
CombinedVibration effect, VibrationAttributes attrs) throws InterruptedException {
- Vibration vib =
+ HalVibration vib =
service.vibrateInternal(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME, effect, attrs,
"some reason", service);
if (vib != null) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 20e8cd4..23587c9 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -19,7 +19,6 @@
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.Notification.EXTRA_ALLOW_DURING_SETUP;
import static android.app.Notification.FLAG_AUTO_CANCEL;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_CAN_COLORIZE;
@@ -218,7 +217,6 @@
import com.android.server.notification.NotificationManagerService.NotificationListeners;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.UserManagerInternal;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
@@ -307,9 +305,6 @@
ActivityManager mActivityManager;
@Mock
TelecomManager mTelecomManager;
-
- @Mock
- PermissionManagerServiceInternal mPermissionInternal;
@Mock
Resources mResources;
@Mock
@@ -523,7 +518,7 @@
mAppOpsManager, mAppOpsService, mUm, mHistoryManager, mStatsManager,
mock(TelephonyManager.class),
mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class),
- mTelecomManager, mLogger, mPermissionInternal);
+ mTelecomManager, mLogger);
// Return first true for RoleObserver main-thread check
when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
@@ -1651,6 +1646,46 @@
}
@Test
+ public void testEnqueueNotificationWithTag_nullAction_fixed() throws Exception {
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .addAction(new Notification.Action.Builder(null, "one", null).build())
+ .addAction(new Notification.Action.Builder(null, "two", null).build())
+ .addAction(new Notification.Action.Builder(null, "three", null).build())
+ .build();
+ n.actions[1] = null;
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, 0);
+ waitForIdle();
+
+ StatusBarNotification[] posted = mBinderService.getActiveNotifications(PKG);
+ assertThat(posted).hasLength(1);
+ assertThat(posted[0].getNotification().actions).hasLength(2);
+ assertThat(posted[0].getNotification().actions[0].title.toString()).isEqualTo("one");
+ assertThat(posted[0].getNotification().actions[1].title.toString()).isEqualTo("three");
+ }
+
+ @Test
+ public void testEnqueueNotificationWithTag_allNullActions_fixed() throws Exception {
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .addAction(new Notification.Action.Builder(null, "one", null).build())
+ .addAction(new Notification.Action.Builder(null, "two", null).build())
+ .build();
+ n.actions[0] = null;
+ n.actions[1] = null;
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, 0);
+ waitForIdle();
+
+ StatusBarNotification[] posted = mBinderService.getActiveNotifications(PKG);
+ assertThat(posted).hasLength(1);
+ assertThat(posted[0].getNotification().actions).isNull();
+ }
+
+ @Test
public void testCancelNonexistentNotification() throws Exception {
mBinderService.cancelNotificationWithTag(PKG, PKG,
"testCancelNonexistentNotification", 0, 0);
@@ -4173,35 +4208,8 @@
}
@Test
- public void testNoNotificationDuringSetupPermission() throws Exception {
- when(mPermissionInternal.checkPermission(any(), any(), anyInt()))
- .thenReturn(PERMISSION_DENIED);
- Bundle extras = new Bundle();
- extras.putBoolean(EXTRA_ALLOW_DURING_SETUP, true);
- Notification.Builder nb = new Notification.Builder(mContext,
- mTestNotificationChannel.getId())
- .setContentTitle("foo")
- .addExtras(extras)
- .setSmallIcon(android.R.drawable.sym_def_app_icon);
- StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
- "testNoNotificationDuringSetupPermission", mUid, 0,
- nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
- NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
-
- mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
- nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
- waitForIdle();
-
- NotificationRecord posted = mService.findNotificationLocked(
- PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
-
- assertFalse(posted.getNotification().extras.containsKey(EXTRA_ALLOW_DURING_SETUP));
- }
-
- @Test
public void testNoFakeColorizedPermission() throws Exception {
- when(mPermissionInternal.checkPermission(any(), any(), anyInt()))
- .thenReturn(PERMISSION_DENIED);
+ when(mPackageManagerClient.checkPermission(any(), any())).thenReturn(PERMISSION_DENIED);
Notification.Builder nb = new Notification.Builder(mContext,
mTestNotificationChannel.getId())
.setContentTitle("foo")
@@ -4226,7 +4234,7 @@
@Test
public void testMediaStyleRemote_hasPermission() throws RemoteException {
String deviceName = "device";
- when(mPermissionInternal.checkPermission(
+ when(mPackageManager.checkPermission(
eq(android.Manifest.permission.MEDIA_CONTENT_CONTROL), any(), anyInt()))
.thenReturn(PERMISSION_GRANTED);
Notification.MediaStyle style = new Notification.MediaStyle();
@@ -4255,7 +4263,7 @@
@Test
public void testMediaStyleRemote_noPermission() throws RemoteException {
String deviceName = "device";
- when(mPermissionInternal.checkPermission(
+ when(mPackageManager.checkPermission(
eq(android.Manifest.permission.MEDIA_CONTENT_CONTROL), any(), anyInt()))
.thenReturn(PERMISSION_DENIED);
Notification.MediaStyle style = new Notification.MediaStyle();
@@ -4283,7 +4291,7 @@
@Test
public void testSubstituteAppName_hasPermission() throws RemoteException {
String subName = "Substitute Name";
- when(mPermissionInternal.checkPermission(
+ when(mPackageManager.checkPermission(
eq(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME), any(), anyInt()))
.thenReturn(PERMISSION_GRANTED);
Bundle extras = new Bundle();
@@ -4310,7 +4318,7 @@
@Test
public void testSubstituteAppName_noPermission() throws RemoteException {
- when(mPermissionInternal.checkPermission(
+ when(mPackageManager.checkPermission(
eq(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME), any(), anyInt()))
.thenReturn(PERMISSION_DENIED);
Bundle extras = new Bundle();
@@ -5820,7 +5828,7 @@
@Test
public void testCanUseManagedServices_hasPermission() throws Exception {
- when(mPermissionInternal.checkPermission("perm", "pkg", 0))
+ when(mPackageManager.checkPermission("perm", "pkg", 0))
.thenReturn(PackageManager.PERMISSION_GRANTED);
assertEquals(true, mService.canUseManagedServices("pkg", 0, "perm"));
@@ -5828,7 +5836,7 @@
@Test
public void testCanUseManagedServices_noPermission() throws Exception {
- when(mPermissionInternal.checkPermission("perm", "pkg", 0))
+ when(mPackageManager.checkPermission("perm", "pkg", 0))
.thenReturn(PackageManager.PERMISSION_DENIED);
assertEquals(false, mService.canUseManagedServices("pkg", 0, "perm"));
@@ -6783,7 +6791,7 @@
private void setIfPackageHasPermissionToAvoidToastRateLimiting(
String pkg, boolean hasPermission) throws Exception {
- when(mPermissionInternal.checkPermission(android.Manifest.permission.UNLIMITED_TOASTS,
+ when(mPackageManager.checkPermission(android.Manifest.permission.UNLIMITED_TOASTS,
pkg, UserHandle.getUserId(mUid)))
.thenReturn(hasPermission ? PERMISSION_GRANTED : PERMISSION_DENIED);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 3a01c2d..98c156e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -72,7 +72,6 @@
import com.android.server.lights.LightsManager;
import com.android.server.notification.NotificationManagerService.NotificationAssistants;
import com.android.server.notification.NotificationManagerService.NotificationListeners;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.utils.quota.MultiRateLimiter;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -169,9 +168,8 @@
mock(StatsManager.class), mock(TelephonyManager.class),
mock(ActivityManagerInternal.class),
mock(MultiRateLimiter.class), mock(PermissionHelper.class),
- mock(UsageStatsManagerInternal.class), mock(TelecomManager.class),
- mock(NotificationChannelLogger.class),
- mock(PermissionManagerServiceInternal.class));
+ mock(UsageStatsManagerInternal.class), mock (TelecomManager.class),
+ mock(NotificationChannelLogger.class));
} catch (SecurityException e) {
if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
throw e;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 49edde5..12f124e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -525,6 +525,13 @@
@Test
public void testZenUpgradeNotification() {
+ /**
+ * Commit a485ec65b5ba947d69158ad90905abf3310655cf disabled DND status change
+ * notification on watches. So, assume that the device is not watch.
+ */
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(false);
+
// shows zen upgrade notification if stored settings says to shows,
// zen has not been updated, boot is completed
// and we're setting zen mode on
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index e663245..56d59b4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -48,7 +48,7 @@
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.Process.NOBODY_UID;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
@@ -3177,18 +3177,18 @@
public void testImeInsetsFrozenFlag_resetWhenReportedToBeImeInputTarget() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindowContainer(
+ mDisplayContent.getInsetsStateController().getSourceProvider(ID_IME).setWindowContainer(
mImeWindow, null, null);
mImeWindow.getControllableInsetProvider().setServerVisible(true);
- InsetsSource imeSource = new InsetsSource(ITYPE_IME, ime());
+ InsetsSource imeSource = new InsetsSource(ID_IME, ime());
app.mAboveInsetsState.addSource(imeSource);
mDisplayContent.setImeLayeringTarget(app);
mDisplayContent.updateImeInputAndControlTarget(app);
InsetsState state = app.getInsetsState();
- assertFalse(state.getSource(imeSource.getId()).isVisible());
- assertTrue(state.getSource(imeSource.getId()).getFrame().isEmpty());
+ assertFalse(state.getOrCreateSource(imeSource.getId(), ime()).isVisible());
+ assertTrue(state.getOrCreateSource(imeSource.getId(), ime()).getFrame().isEmpty());
// Simulate app is closing and expect IME insets is frozen.
mDisplayContent.mOpeningApps.clear();
@@ -3211,8 +3211,8 @@
// Verify when IME is visible and the app can receive the right IME insets from policy.
makeWindowVisibleAndDrawn(app, mImeWindow);
state = app.getInsetsState();
- assertTrue(state.getSource(ITYPE_IME).isVisible());
- assertEquals(state.getSource(ITYPE_IME).getFrame(), imeSource.getFrame());
+ assertTrue(state.peekSource(ID_IME).isVisible());
+ assertEquals(state.peekSource(ID_IME).getFrame(), imeSource.getFrame());
}
@SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
@@ -3222,7 +3222,7 @@
final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1");
final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2");
- mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindowContainer(
+ mDisplayContent.getInsetsStateController().getSourceProvider(ID_IME).setWindowContainer(
mImeWindow, null, null);
mImeWindow.getControllableInsetProvider().setServerVisible(true);
@@ -3237,7 +3237,7 @@
mDisplayContent.getInsetsStateController().onInsetsModified(app1);
// Verify app1's IME insets is visible and app2's IME insets frozen flag set.
- assertTrue(app1.getInsetsState().peekSource(ITYPE_IME).isVisible());
+ assertTrue(app1.getInsetsState().peekSource(ID_IME).isVisible());
assertTrue(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput);
// Simulate switching to app2 to make it visible to be IME targets.
@@ -3256,7 +3256,7 @@
verify(app2.mClient, atLeastOnce()).resized(any(), anyBoolean(), any(),
insetsStateCaptor.capture(), anyBoolean(), anyBoolean(), anyInt(), anyInt(),
anyBoolean());
- assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
+ assertFalse(app2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
}
@Test
@@ -3288,7 +3288,7 @@
makeWindowVisibleAndDrawn(app1, app2);
final InsetsStateController controller = mDisplayContent.getInsetsStateController();
- controller.getSourceProvider(ITYPE_IME).setWindowContainer(
+ controller.getSourceProvider(ID_IME).setWindowContainer(
ime, null, null);
ime.getControllableInsetProvider().setServerVisible(true);
@@ -3306,8 +3306,8 @@
controller.onInsetsModified(app1);
// Expect all activities in split-screen will get IME insets visible state
- assertTrue(app1.getInsetsState().peekSource(ITYPE_IME).isVisible());
- assertTrue(app2.getInsetsState().peekSource(ITYPE_IME).isVisible());
+ assertTrue(app1.getInsetsState().peekSource(ID_IME).isVisible());
+ assertTrue(app2.getInsetsState().peekSource(ID_IME).isVisible());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 3b34ba4..abc0c14 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1557,13 +1557,13 @@
// If the visibility of insets state is changed, the rotated state should be updated too.
final InsetsState rotatedState = app.getFixedRotationTransformInsetsState();
final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
- assertEquals(state.getSource(ITYPE_STATUS_BAR).isVisible(),
- rotatedState.getSource(ITYPE_STATUS_BAR).isVisible());
- state.getSource(ITYPE_STATUS_BAR).setVisible(
- !rotatedState.getSource(ITYPE_STATUS_BAR).isVisible());
+ assertEquals(state.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()),
+ rotatedState.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
+ state.setSourceVisible(ITYPE_STATUS_BAR,
+ !rotatedState.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
mDisplayContent.getInsetsStateController().notifyInsetsChanged();
- assertEquals(state.getSource(ITYPE_STATUS_BAR).isVisible(),
- rotatedState.getSource(ITYPE_STATUS_BAR).isVisible());
+ assertEquals(state.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()),
+ rotatedState.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
final Rect outFrame = new Rect();
final Rect outInsets = new Rect();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 6733470..45cf530 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -43,6 +43,7 @@
import android.platform.test.annotations.Presubmit;
import android.view.DisplayInfo;
import android.view.InsetsFrameProvider;
+import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.PrivacyIndicatorBounds;
import android.view.RoundedCorners;
@@ -255,7 +256,7 @@
mDisplayContent.getInsetsStateController().getRawInsetsState());
// Exclude comparing IME insets because currently the simulated layout only focuses on the
// insets from status bar and navigation bar.
- realInsetsState.removeSource(InsetsState.ITYPE_IME);
+ realInsetsState.removeSource(InsetsSource.ID_IME);
realInsetsState.removeSource(InsetsState.ITYPE_CAPTION_BAR);
assertEquals(new ToStringComparatorWrapper<>(realInsetsState),
@@ -270,9 +271,9 @@
.rotationForActivityInDifferentOrientation(eq(mWindow.mActivityRecord));
mWindow.mAboveInsetsState.set(
mDisplayContent.getInsetsStateController().getRawInsetsState());
- final Rect frame = mWindow.getInsetsState().getSource(ITYPE_STATUS_BAR).getFrame();
+ final Rect frame = mWindow.getInsetsState().peekSource(ITYPE_STATUS_BAR).getFrame();
mDisplayContent.rotateInDifferentOrientationIfNeeded(mWindow.mActivityRecord);
- final Rect rotatedFrame = mWindow.getInsetsState().getSource(ITYPE_STATUS_BAR).getFrame();
+ final Rect rotatedFrame = mWindow.getInsetsState().peekSource(ITYPE_STATUS_BAR).getFrame();
assertEquals(DISPLAY_WIDTH, frame.width());
assertEquals(DISPLAY_HEIGHT, rotatedFrame.width());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index cf9ec81..6bb7769 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -17,8 +17,8 @@
package com.android.server.wm;
import static android.view.DisplayCutout.NO_CUTOUT;
+import static android.view.InsetsSource.ID_IME;
import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.RoundedCorners.NO_ROUNDED_CORNERS;
import static android.view.Surface.ROTATION_0;
@@ -335,7 +335,7 @@
displayPolicy.layoutWindowLw(mNavBarWindow, null, mDisplayContent.mDisplayFrames);
displayPolicy.layoutWindowLw(mImeWindow, null, mDisplayContent.mDisplayFrames);
- final InsetsSource imeSource = state.peekSource(ITYPE_IME);
+ final InsetsSource imeSource = state.peekSource(ID_IME);
final InsetsSource navBarSource = state.peekSource(ITYPE_NAVIGATION_BAR);
assertNotNull(imeSource);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 45b30b2..4954e89 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
@@ -30,7 +31,9 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertEquals;
@@ -58,6 +61,8 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.R;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -128,6 +133,69 @@
}
@Test
+ public void testOpenedCameraInSplitScreen_showToast() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ spyOn(mTask);
+ spyOn(mDisplayRotationCompatPolicy);
+ doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mActivity).getWindowingMode();
+ doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mTask).getWindowingMode();
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ verify(mDisplayRotationCompatPolicy).showToast(
+ R.string.display_rotation_camera_compat_toast_in_split_screen);
+ }
+
+ @Test
+ public void testOnScreenRotationAnimationFinished_treatmentNotEnabled_doNotShowToast() {
+ when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+ /* checkDeviceConfig */ anyBoolean()))
+ .thenReturn(false);
+ spyOn(mDisplayRotationCompatPolicy);
+
+ mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
+
+ verify(mDisplayRotationCompatPolicy, never()).showToast(
+ R.string.display_rotation_camera_compat_toast_after_rotation);
+ }
+
+ @Test
+ public void testOnScreenRotationAnimationFinished_noOpenCamera_doNotShowToast() {
+ spyOn(mDisplayRotationCompatPolicy);
+
+ mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
+
+ verify(mDisplayRotationCompatPolicy, never()).showToast(
+ R.string.display_rotation_camera_compat_toast_after_rotation);
+ }
+
+ @Test
+ public void testOnScreenRotationAnimationFinished_notFullscreen_doNotShowToast() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mActivity).getWindowingMode();
+ spyOn(mDisplayRotationCompatPolicy);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
+
+ verify(mDisplayRotationCompatPolicy, never()).showToast(
+ R.string.display_rotation_camera_compat_toast_after_rotation);
+ }
+
+ @Test
+ public void testOnScreenRotationAnimationFinished_showToast() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ spyOn(mDisplayRotationCompatPolicy);
+
+ mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
+
+ verify(mDisplayRotationCompatPolicy).showToast(
+ R.string.display_rotation_camera_compat_toast_after_rotation);
+ }
+
+ @Test
public void testTreatmentNotEnabled_noForceRotationOrRefresh() throws Exception {
when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
/* checkDeviceConfig */ anyBoolean()))
diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index d4e860e..20bb549 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
@@ -39,7 +39,7 @@
@RunWith(WindowTestRunner.class)
public class ImeInsetsSourceProviderTest extends WindowTestsBase {
- private InsetsSource mImeSource = new InsetsSource(ITYPE_IME, ime());
+ private InsetsSource mImeSource = new InsetsSource(ID_IME, ime());
private ImeInsetsSourceProvider mImeProvider;
@Before
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index 9887839..dba2995 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -276,9 +276,9 @@
policy.updateBarControlTarget(mAppWindow);
waitUntilWindowAnimatorIdle();
assertFalse(mDisplayContent.getInsetsStateController().getRawInsetsState()
- .getSource(ITYPE_STATUS_BAR).isVisible());
+ .isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
assertFalse(mDisplayContent.getInsetsStateController().getRawInsetsState()
- .getSource(ITYPE_NAVIGATION_BAR).isVisible());
+ .isSourceOrDefaultVisible(ITYPE_NAVIGATION_BAR, navigationBars()));
policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR},
true /* isGestureOnSystemBar */);
@@ -293,9 +293,9 @@
}
assertTrue(mDisplayContent.getInsetsStateController().getRawInsetsState()
- .getSource(ITYPE_STATUS_BAR).isVisible());
+ .isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
assertTrue(mDisplayContent.getInsetsStateController().getRawInsetsState()
- .getSource(ITYPE_NAVIGATION_BAR).isVisible());
+ .isSourceOrDefaultVisible(ITYPE_NAVIGATION_BAR, navigationBars()));
}
@SetupWindows(addWindows = W_ACTIVITY)
@@ -362,11 +362,11 @@
final InsetsState clientState = mAppWindow.getInsetsState();
// The transient bar states for client should be invisible.
- assertFalse(clientState.getSource(ITYPE_STATUS_BAR).isVisible());
- assertFalse(clientState.getSource(ITYPE_NAVIGATION_BAR).isVisible());
+ assertFalse(clientState.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
+ assertFalse(clientState.isSourceOrDefaultVisible(ITYPE_NAVIGATION_BAR, navigationBars()));
// The original state shouldn't be modified.
- assertTrue(state.getSource(ITYPE_STATUS_BAR).isVisible());
- assertTrue(state.getSource(ITYPE_NAVIGATION_BAR).isVisible());
+ assertTrue(state.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
+ assertTrue(state.isSourceOrDefaultVisible(ITYPE_NAVIGATION_BAR, navigationBars()));
mAppWindow.setRequestedVisibleTypes(
navigationBars() | statusBars(), navigationBars() | statusBars());
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 4d69979..88ecd3f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -19,12 +19,13 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.InsetsSource.ID_IME;
import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -76,9 +77,9 @@
null);
getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null,
null);
- getController().getSourceProvider(ITYPE_IME).setWindowContainer(ime, null, null);
+ getController().getSourceProvider(ID_IME).setWindowContainer(ime, null, null);
- assertNull(navBar.getInsetsState().peekSource(ITYPE_IME));
+ assertNull(navBar.getInsetsState().peekSource(ID_IME));
assertNull(navBar.getInsetsState().peekSource(ITYPE_STATUS_BAR));
}
@@ -96,7 +97,7 @@
assertNull(app.getInsetsState().peekSource(ITYPE_STATUS_BAR));
assertNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR));
- assertNull(app.getInsetsState().peekSource(ITYPE_IME));
+ assertNull(app.getInsetsState().peekSource(ID_IME));
}
@Test
@@ -135,43 +136,41 @@
@SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_independentSources() {
- getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
+ getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null);
final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1");
final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2");
- app1.mAboveInsetsState.addSource(getController().getRawInsetsState().getSource(ITYPE_IME));
+ app1.mAboveInsetsState.addSource(getController().getRawInsetsState().peekSource(ID_IME));
- getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
- assertFalse(app2.getInsetsState().getSource(ITYPE_IME)
- .isVisible());
- assertTrue(app1.getInsetsState().getSource(ITYPE_IME)
- .isVisible());
+ getController().getRawInsetsState().setSourceVisible(ID_IME, true);
+ assertFalse(app2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+ assertTrue(app1.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
}
@SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_belowIme() {
- getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
+ getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null);
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- app.mAboveInsetsState.getSource(ITYPE_IME).setVisible(true);
- app.mAboveInsetsState.getSource(ITYPE_IME).setFrame(mImeWindow.getFrame());
+ app.mAboveInsetsState.getOrCreateSource(ID_IME, ime())
+ .setVisible(true)
+ .setFrame(mImeWindow.getFrame());
- getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
- assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
+ getController().getRawInsetsState().setSourceVisible(ID_IME, true);
+ assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
}
@SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_aboveIme() {
- getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
+ getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null);
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
- assertFalse(app.getInsetsState().getSource(ITYPE_IME)
- .isVisible());
+ getController().getRawInsetsState().setSourceVisible(ID_IME, true);
+ assertFalse(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
}
@SetupWindows(addWindows = W_INPUT_METHOD)
@@ -185,7 +184,7 @@
// Make IME and stay visible during the test.
mImeWindow.setHasSurface(true);
- getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
+ getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null);
getController().onImeControlTargetChanged(
mDisplayContent.getImeInputTarget().getWindowState());
mDisplayContent.getImeInputTarget().getWindowState().setRequestedVisibleTypes(ime(), ime());
@@ -205,9 +204,8 @@
mDisplayContent.applySurfaceChangesTransaction();
// app won't get visible IME insets while above IME even when IME is visible.
- assertTrue(getController().getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME));
- assertFalse(app.getInsetsState().getSource(ITYPE_IME)
- .isVisible());
+ assertTrue(getController().getRawInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+ assertFalse(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
// Reset invocation counter.
clearInvocations(app);
@@ -216,20 +214,21 @@
app.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE;
mDisplayContent.computeImeTarget(true);
mDisplayContent.applySurfaceChangesTransaction();
- app.mAboveInsetsState.getSource(ITYPE_IME).setVisible(true);
- app.mAboveInsetsState.getSource(ITYPE_IME).setFrame(mImeWindow.getFrame());
+ app.mAboveInsetsState.getOrCreateSource(ID_IME, ime())
+ .setVisible(true)
+ .setFrame(mImeWindow.getFrame());
// Make sure app got notified.
verify(app, atLeastOnce()).notifyInsetsChanged();
// app will get visible IME insets while below IME.
- assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
+ assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
}
@SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_childWindow_altFocusable() {
- getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
+ getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null);
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
final WindowState child = createWindow(app, TYPE_APPLICATION, "child");
@@ -241,20 +240,19 @@
mDisplayContent.setLayoutNeeded();
mDisplayContent.applySurfaceChangesTransaction();
- getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
- assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
- assertFalse(child.getInsetsState().getSource(ITYPE_IME)
- .isVisible());
+ getController().getRawInsetsState().setSourceVisible(ID_IME, true);
+ assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+ assertFalse(child.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
}
@SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_childWindow_splitScreen() {
- getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
+ getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null);
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
final WindowState child = createWindow(app, TYPE_APPLICATION, "child");
- app.mAboveInsetsState.addSource(getController().getRawInsetsState().peekSource(ITYPE_IME));
+ app.mAboveInsetsState.addSource(getController().getRawInsetsState().peekSource(ID_IME));
child.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
child.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
@@ -262,10 +260,9 @@
mDisplayContent.setLayoutNeeded();
mDisplayContent.applySurfaceChangesTransaction();
- getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
- assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
- assertFalse(child.getInsetsState().getSource(ITYPE_IME)
- .isVisible());
+ getController().getRawInsetsState().setSourceVisible(ID_IME, true);
+ assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+ assertFalse(child.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
}
@Test
@@ -283,14 +280,14 @@
imeOverrideProviders.put(TYPE_INPUT_METHOD, ((displayFrames, windowState, rect) ->
rect.set(0, 1, 2, 3)));
statusBarProvider.setWindowContainer(statusBar, null, imeOverrideProviders);
- getController().getSourceProvider(ITYPE_IME).setWindowContainer(ime, null, null);
+ getController().getSourceProvider(ID_IME).setWindowContainer(ime, null, null);
statusBar.setControllableInsetProvider(statusBarProvider);
statusBar.updateSourceFrame(statusBar.getFrame());
statusBarProvider.onPostLayout();
final InsetsState state = ime.getInsetsState();
- assertEquals(new Rect(0, 1, 2, 3), state.getSource(ITYPE_STATUS_BAR).getFrame());
+ assertEquals(new Rect(0, 1, 2, 3), state.peekSource(ITYPE_STATUS_BAR).getFrame());
}
@Test
@@ -348,16 +345,17 @@
final InsetsState rotatedState = new InsetsState(app.getInsetsState(),
true /* copySources */);
+ rotatedState.getOrCreateSource(ITYPE_STATUS_BAR, statusBars());
spyOn(app.mToken);
doReturn(rotatedState).when(app.mToken).getFixedRotationTransformInsetsState();
- assertTrue(rotatedState.getSource(ITYPE_STATUS_BAR).isVisible());
+ assertTrue(rotatedState.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
provider.getSource().setVisible(false);
mDisplayContent.getInsetsPolicy().showTransient(new int[] { ITYPE_STATUS_BAR },
true /* isGestureOnSystemBar */);
assertTrue(mDisplayContent.getInsetsPolicy().isTransient(ITYPE_STATUS_BAR));
- assertFalse(app.getInsetsState().getSource(ITYPE_STATUS_BAR).isVisible());
+ assertFalse(app.getInsetsState().isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
}
@Test
@@ -411,11 +409,11 @@
final WindowState statusBar = createTestWindow("statusBar");
final WindowState navBar = createTestWindow("navBar");
- getController().getSourceProvider(ITYPE_IME).setWindowContainer(ime, null, null);
+ getController().getSourceProvider(ID_IME).setWindowContainer(ime, null, null);
waitUntilHandlersIdle();
clearInvocations(mDisplayContent);
- getController().getSourceProvider(ITYPE_IME).setClientVisible(true);
+ getController().getSourceProvider(ID_IME).setClientVisible(true);
waitUntilHandlersIdle();
// The visibility change should trigger a traversal to notify the change.
verify(mDisplayContent).notifyInsetsChanged(any());
@@ -428,9 +426,9 @@
getController().updateAboveInsetsState(false /* notifyInsetsChange */);
// ime is below others.
- assertNull(app.mAboveInsetsState.peekSource(ITYPE_IME));
- assertNull(statusBar.mAboveInsetsState.peekSource(ITYPE_IME));
- assertNull(navBar.mAboveInsetsState.peekSource(ITYPE_IME));
+ assertNull(app.mAboveInsetsState.peekSource(ID_IME));
+ assertNull(statusBar.mAboveInsetsState.peekSource(ID_IME));
+ assertNull(navBar.mAboveInsetsState.peekSource(ID_IME));
assertNotNull(ime.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
assertNotNull(ime.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR));
@@ -438,9 +436,9 @@
getController().updateAboveInsetsState(true /* notifyInsetsChange */);
// ime is above others.
- assertNotNull(app.mAboveInsetsState.peekSource(ITYPE_IME));
- assertNotNull(statusBar.mAboveInsetsState.peekSource(ITYPE_IME));
- assertNotNull(navBar.mAboveInsetsState.peekSource(ITYPE_IME));
+ assertNotNull(app.mAboveInsetsState.peekSource(ID_IME));
+ assertNotNull(statusBar.mAboveInsetsState.peekSource(ID_IME));
+ assertNotNull(navBar.mAboveInsetsState.peekSource(ID_IME));
assertNull(ime.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
assertNull(ime.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR));
@@ -456,7 +454,7 @@
final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, imeToken, "ime");
final WindowState app = createTestWindow("app");
- getController().getSourceProvider(ITYPE_IME).setWindowContainer(ime, null, null);
+ getController().getSourceProvider(ID_IME).setWindowContainer(ime, null, null);
ime.getControllableInsetProvider().setServerVisible(true);
app.mActivityRecord.setVisibility(true);
@@ -468,7 +466,7 @@
assertTrue(ime.getControllableInsetProvider().getSource().isVisible());
getController().updateAboveInsetsState(true /* notifyInsetsChange */);
- assertNotNull(app.getInsetsState().peekSource(ITYPE_IME));
+ assertNotNull(app.getInsetsState().peekSource(ID_IME));
verify(app, atLeastOnce()).notifyInsetsChanged();
// Expect the app will still get IME insets even when the app was invisible.
@@ -476,7 +474,7 @@
app.mActivityRecord.setVisible(false);
app.setHasSurface(false);
getController().updateAboveInsetsState(true /* notifyInsetsChange */);
- assertNotNull(app.getInsetsState().peekSource(ITYPE_IME));
+ assertNotNull(app.getInsetsState().peekSource(ID_IME));
verify(app, atLeastOnce()).notifyInsetsChanged();
// Expect the app will get IME insets when the app is requesting visible.
@@ -484,7 +482,7 @@
app.mActivityRecord.setVisibility(true);
assertTrue(app.isVisibleRequested());
getController().updateAboveInsetsState(true /* notifyInsetsChange */);
- assertNotNull(app.getInsetsState().peekSource(ITYPE_IME));
+ assertNotNull(app.getInsetsState().peekSource(ID_IME));
verify(app, atLeastOnce()).notifyInsetsChanged();
}
@@ -506,7 +504,7 @@
final WindowState app2 = createTestWindow("app2");
makeWindowVisible(mImeWindow);
- final InsetsSourceProvider imeInsetsProvider = getController().getSourceProvider(ITYPE_IME);
+ final InsetsSourceProvider imeInsetsProvider = getController().getSourceProvider(ID_IME);
imeInsetsProvider.setWindowContainer(mImeWindow, null, null);
imeInsetsProvider.updateSourceFrame(mImeWindow.getFrame());
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
index ead1a86..12b7c9d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -51,7 +51,7 @@
* Tests for the {@link LetterboxConfiguration} class.
*
* Build/Install/Run:
- * atest WmTests:LetterboxConfigurationTests
+ * atest WmTests:LetterboxConfigurationTest
*/
@SmallTest
@Presubmit
@@ -243,18 +243,18 @@
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, "true", false);
mLetterboxConfiguration.setIsCompatFakeFocusEnabled(false);
- assertFalse(mLetterboxConfiguration.isCompatFakeFocusEnabledOnDevice());
+ assertFalse(mLetterboxConfiguration.isCompatFakeFocusEnabled());
// Set runtime flag to false and build time flag to true
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, "false", false);
mLetterboxConfiguration.setIsCompatFakeFocusEnabled(true);
- assertFalse(mLetterboxConfiguration.isCompatFakeFocusEnabledOnDevice());
+ assertFalse(mLetterboxConfiguration.isCompatFakeFocusEnabled());
// Set runtime flag to true so that both are enabled
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, "true", false);
- assertTrue(mLetterboxConfiguration.isCompatFakeFocusEnabledOnDevice());
+ assertTrue(mLetterboxConfiguration.isCompatFakeFocusEnabled());
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, Boolean.toString(wasFakeFocusEnabled),
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 110ef89..d8037f6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -20,8 +20,10 @@
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
+import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT;
import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
@@ -37,6 +39,7 @@
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -71,6 +74,7 @@
import com.android.internal.R;
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
import org.junit.Before;
@@ -579,6 +583,42 @@
/* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
}
+ @Test
+ @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT,
+ OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
+ public void testOverrideOrientationIfNeeded_whenCameraNotActive_returnsUnchanged() {
+ doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(anyBoolean());
+
+ // Recreate DisplayContent with DisplayRotationCompatPolicy
+ mActivity = setUpActivityWithComponent();
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ spyOn(mDisplayContent.mDisplayRotationCompatPolicy);
+ doReturn(false).when(mDisplayContent.mDisplayRotationCompatPolicy)
+ .isActivityEligibleForOrientationOverride(eq(mActivity));
+
+ assertEquals(mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT,
+ OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
+ public void testOverrideOrientationIfNeeded_whenCameraActive_returnsPortrait() {
+ doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(anyBoolean());
+
+ // Recreate DisplayContent with DisplayRotationCompatPolicy
+ mActivity = setUpActivityWithComponent();
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ spyOn(mDisplayContent.mDisplayRotationCompatPolicy);
+ doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy)
+ .isActivityEligibleForOrientationOverride(eq(mActivity));
+
+ assertEquals(mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT);
+ }
+
// shouldUseDisplayLandscapeNaturalOrientation
@Test
@@ -629,6 +669,72 @@
assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation());
}
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testShouldSendFakeFocus_overrideEnabled_returnsTrue() {
+ doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled();
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldSendFakeFocus());
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testShouldSendFakeFocus_overrideDisabled_returnsFalse() {
+ doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled();
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldSendFakeFocus());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testIsCompatFakeFocusEnabled_propertyDisabledAndOverrideEnabled_fakeFocusDisabled()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled();
+ mockThatProperty(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldSendFakeFocus());
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+ public void testIsCompatFakeFocusEnabled_propertyEnabled_noOverride_fakeFocusEnabled()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled();
+ mockThatProperty(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldSendFakeFocus());
+ }
+
+ @Test
+ public void testIsCompatFakeFocusEnabled_propertyDisabled_fakeFocusDisabled()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled();
+ mockThatProperty(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldSendFakeFocus());
+ }
+
+ @Test
+ public void testIsCompatFakeFocusEnabled_propertyEnabled_fakeFocusEnabled()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled();
+ mockThatProperty(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldSendFakeFocus());
+ }
+
private void mockThatProperty(String propertyName, boolean value) throws Exception {
Property property = new Property(propertyName, /* value */ value, /* packageName */ "",
/* className */ "");
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 1ab8c1f..7ad5442 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -16,8 +16,10 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
@@ -56,8 +58,6 @@
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityRecord.State.STOPPED;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
-import static com.android.server.wm.LetterboxConfiguration.PROPERTY_COMPAT_FAKE_FOCUS_OPT_IN;
-import static com.android.server.wm.LetterboxConfiguration.PROPERTY_COMPAT_FAKE_FOCUS_OPT_OUT;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.google.common.truth.Truth.assertThat;
@@ -2332,6 +2332,29 @@
}
@Test
+ public void testDisplayIgnoreOrientationRequest_disabledViaDeviceConfig_orientationRespected() {
+ // Set up a display in landscape
+ setUpDisplaySizeWithApp(2800, 1400);
+
+ final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */ false,
+ RESIZE_MODE_UNRESIZEABLE, SCREEN_ORIENTATION_PORTRAIT);
+ activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+ spyOn(activity.mWmService.mLetterboxConfiguration);
+ doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
+ .isIgnoreOrientationRequestAllowed();
+
+ // Display should not be rotated.
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, activity.mDisplayContent.getOrientation());
+
+ doReturn(false).when(activity.mWmService.mLetterboxConfiguration)
+ .isIgnoreOrientationRequestAllowed();
+
+ // Display should be rotated.
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, activity.mDisplayContent.getOrientation());
+ }
+
+ @Test
public void testSandboxDisplayApis_unresizableAppNotSandboxed() {
// Set up a display in landscape with an unresizable app.
setUpDisplaySizeWithApp(2500, 1000);
@@ -3631,7 +3654,8 @@
assertEquals(newDensity, mActivity.getConfiguration().densityDpi);
}
- private ActivityRecord setUpActivityForCompatFakeFocusTest() {
+ @Test
+ public void testShouldSendFakeFocus_compatFakeFocusEnabled() {
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setCreateTask(true)
.setOnTop(true)
@@ -3640,69 +3664,40 @@
com.android.server.wm.SizeCompatTests.class.getName()))
.build();
final Task task = activity.getTask();
+ spyOn(activity.mLetterboxUiController);
+ doReturn(true).when(activity.mLetterboxUiController).shouldSendFakeFocus();
+
task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- spyOn(activity.mWmService.mLetterboxConfiguration);
- doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
- .isCompatFakeFocusEnabledOnDevice();
- return activity;
- }
-
- @Test
- @EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
- public void testShouldSendFakeFocus_overrideEnabled_returnsTrue() {
- ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
-
assertTrue(activity.shouldSendCompatFakeFocus());
- }
- @Test
- @DisableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
- public void testShouldSendFakeFocus_overrideDisabled_returnsFalse() {
- ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
+ task.setWindowingMode(WINDOWING_MODE_PINNED);
+ assertFalse(activity.shouldSendCompatFakeFocus());
+ task.setWindowingMode(WINDOWING_MODE_FREEFORM);
assertFalse(activity.shouldSendCompatFakeFocus());
}
@Test
- @EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
- public void testIsCompatFakeFocusEnabled_optOutPropertyAndOverrideEnabled_fakeFocusDisabled() {
- ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
- doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
- .getPackageManagerProperty(any(), eq(PROPERTY_COMPAT_FAKE_FOCUS_OPT_OUT));
+ public void testShouldSendFakeFocus_compatFakeFocusDisabled() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setCreateTask(true)
+ .setOnTop(true)
+ // Set the component to be that of the test class in order to enable compat changes
+ .setComponent(ComponentName.createRelative(mContext,
+ com.android.server.wm.SizeCompatTests.class.getName()))
+ .build();
+ final Task task = activity.getTask();
+ spyOn(activity.mLetterboxUiController);
+ doReturn(false).when(activity.mLetterboxUiController).shouldSendFakeFocus();
- assertFalse(activity.mWmService.mLetterboxConfiguration
- .isCompatFakeFocusEnabled(activity.info));
- }
+ task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ assertFalse(activity.shouldSendCompatFakeFocus());
- @Test
- @DisableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
- public void testIsCompatFakeFocusEnabled_optInPropertyEnabled_noOverride_fakeFocusEnabled() {
- ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
- doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
- .getPackageManagerProperty(any(), eq(PROPERTY_COMPAT_FAKE_FOCUS_OPT_IN));
+ task.setWindowingMode(WINDOWING_MODE_PINNED);
+ assertFalse(activity.shouldSendCompatFakeFocus());
- assertTrue(activity.mWmService.mLetterboxConfiguration
- .isCompatFakeFocusEnabled(activity.info));
- }
-
- @Test
- public void testIsCompatFakeFocusEnabled_optOutPropertyEnabled_fakeFocusDisabled() {
- ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
- doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
- .getPackageManagerProperty(any(), eq(PROPERTY_COMPAT_FAKE_FOCUS_OPT_OUT));
-
- assertFalse(activity.mWmService.mLetterboxConfiguration
- .isCompatFakeFocusEnabled(activity.info));
- }
-
- @Test
- public void testIsCompatFakeFocusEnabled_optInPropertyEnabled_fakeFocusEnabled() {
- ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
- doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
- .getPackageManagerProperty(any(), eq(PROPERTY_COMPAT_FAKE_FOCUS_OPT_IN));
-
- assertTrue(activity.mWmService.mLetterboxConfiguration
- .isCompatFakeFocusEnabled(activity.info));
+ task.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ assertFalse(activity.shouldSendCompatFakeFocus());
}
private int getExpectedSplitSize(int dimensionToSplit) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
index 22d72ed..c538727 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
@@ -54,11 +54,11 @@
final CountDownLatch finishedLatch = new CountDownLatch(1);
SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
- SyncTarget syncTarget = new SyncTarget();
+ SurfaceSyncGroup syncTarget = new SurfaceSyncGroup("FakeSyncTarget");
syncGroup.add(syncTarget, null /* runnable */);
syncGroup.markSyncReady();
- syncTarget.onBufferReady();
+ syncTarget.markSyncReady();
finishedLatch.await(5, TimeUnit.SECONDS);
assertEquals(0, finishedLatch.getCount());
@@ -69,22 +69,22 @@
final CountDownLatch finishedLatch = new CountDownLatch(1);
SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
- SyncTarget syncTarget1 = new SyncTarget();
- SyncTarget syncTarget2 = new SyncTarget();
- SyncTarget syncTarget3 = new SyncTarget();
+ SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
+ SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
+ SurfaceSyncGroup syncTarget3 = new SurfaceSyncGroup("FakeSyncTarget3");
syncGroup.add(syncTarget1, null /* runnable */);
syncGroup.add(syncTarget2, null /* runnable */);
syncGroup.add(syncTarget3, null /* runnable */);
syncGroup.markSyncReady();
- syncTarget1.onBufferReady();
+ syncTarget1.markSyncReady();
assertNotEquals(0, finishedLatch.getCount());
- syncTarget3.onBufferReady();
+ syncTarget3.markSyncReady();
assertNotEquals(0, finishedLatch.getCount());
- syncTarget2.onBufferReady();
+ syncTarget2.markSyncReady();
finishedLatch.await(5, TimeUnit.SECONDS);
assertEquals(0, finishedLatch.getCount());
@@ -94,8 +94,8 @@
public void testAddSyncWhenSyncComplete() {
SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
- SyncTarget syncTarget1 = new SyncTarget();
- SyncTarget syncTarget2 = new SyncTarget();
+ SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
+ SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
assertTrue(syncGroup.add(syncTarget1, null /* runnable */));
syncGroup.markSyncReady();
@@ -114,21 +114,21 @@
syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
- SyncTarget syncTarget1 = new SyncTarget();
- SyncTarget syncTarget2 = new SyncTarget();
+ SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
+ SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
assertTrue(syncGroup2.add(syncTarget2, null /* runnable */));
syncGroup1.markSyncReady();
syncGroup2.markSyncReady();
- syncTarget1.onBufferReady();
+ syncTarget1.markSyncReady();
finishedLatch1.await(5, TimeUnit.SECONDS);
assertEquals(0, finishedLatch1.getCount());
assertNotEquals(0, finishedLatch2.getCount());
- syncTarget2.onBufferReady();
+ syncTarget2.markSyncReady();
finishedLatch2.await(5, TimeUnit.SECONDS);
assertEquals(0, finishedLatch2.getCount());
@@ -144,8 +144,8 @@
syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
- SyncTarget syncTarget1 = new SyncTarget();
- SyncTarget syncTarget2 = new SyncTarget();
+ SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
+ SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
assertTrue(syncGroup2.add(syncTarget2, null /* runnable */));
@@ -155,12 +155,12 @@
// Finish syncTarget2 first to test that the syncGroup is not complete until the merged sync
// is also done.
- syncTarget2.onBufferReady();
+ syncTarget2.markSyncReady();
finishedLatch2.await(1, TimeUnit.SECONDS);
// Sync did not complete yet
assertNotEquals(0, finishedLatch2.getCount());
- syncTarget1.onBufferReady();
+ syncTarget1.markSyncReady();
// The first sync will still get a callback when it's sync requirements are done.
finishedLatch1.await(5, TimeUnit.SECONDS);
@@ -180,13 +180,13 @@
syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
- SyncTarget syncTarget1 = new SyncTarget();
- SyncTarget syncTarget2 = new SyncTarget();
+ SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
+ SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
assertTrue(syncGroup2.add(syncTarget2, null /* runnable */));
syncGroup1.markSyncReady();
- syncTarget1.onBufferReady();
+ syncTarget1.markSyncReady();
// The first sync will still get a callback when it's sync requirements are done.
finishedLatch1.await(5, TimeUnit.SECONDS);
@@ -194,7 +194,7 @@
syncGroup2.add(syncGroup1, null /* runnable */);
syncGroup2.markSyncReady();
- syncTarget2.onBufferReady();
+ syncTarget2.markSyncReady();
// Verify that the second sync will receive complete since the merged sync was already
// completed before the merge.
@@ -212,9 +212,9 @@
syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
- SyncTarget syncTarget1 = new SyncTarget();
- SyncTarget syncTarget2 = new SyncTarget();
- SyncTarget syncTarget3 = new SyncTarget();
+ SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
+ SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
+ SurfaceSyncGroup syncTarget3 = new SurfaceSyncGroup("FakeSyncTarget3");
assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
assertTrue(syncGroup1.add(syncTarget2, null /* runnable */));
@@ -228,8 +228,8 @@
// Make target1 and target3 ready, but not target2. SyncGroup2 should not be ready since
// SyncGroup2 also waits for all of SyncGroup1 to finish, which includes target2
- syncTarget1.onBufferReady();
- syncTarget3.onBufferReady();
+ syncTarget1.markSyncReady();
+ syncTarget3.markSyncReady();
// Neither SyncGroup will be ready.
finishedLatch1.await(1, TimeUnit.SECONDS);
@@ -238,7 +238,7 @@
assertEquals(1, finishedLatch1.getCount());
assertEquals(1, finishedLatch2.getCount());
- syncTarget2.onBufferReady();
+ syncTarget2.markSyncReady();
// Both sync groups should be ready after target2 completed.
finishedLatch1.await(5, TimeUnit.SECONDS);
@@ -257,13 +257,13 @@
syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
- SyncTarget syncTarget1 = new SyncTarget();
- SyncTarget syncTarget2 = new SyncTarget();
- SyncTarget syncTarget3 = new SyncTarget();
+ SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
+ SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
+ SurfaceSyncGroup syncTarget3 = new SurfaceSyncGroup("FakeSyncTarget3");
assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
assertTrue(syncGroup1.add(syncTarget2, null /* runnable */));
- syncTarget2.onBufferReady();
+ syncTarget2.markSyncReady();
// Add syncTarget1 to syncGroup2 so it forces syncGroup1 into syncGroup2
assertTrue(syncGroup2.add(syncTarget1, null /* runnable */));
@@ -272,7 +272,7 @@
syncGroup1.markSyncReady();
syncGroup2.markSyncReady();
- syncTarget1.onBufferReady();
+ syncTarget1.markSyncReady();
// Only SyncGroup1 will be ready, but SyncGroup2 still needs its own targets to be ready.
finishedLatch1.await(1, TimeUnit.SECONDS);
@@ -281,7 +281,7 @@
assertEquals(0, finishedLatch1.getCount());
assertEquals(1, finishedLatch2.getCount());
- syncTarget3.onBufferReady();
+ syncTarget3.markSyncReady();
// SyncGroup2 is finished after target3 completed.
finishedLatch2.await(1, TimeUnit.SECONDS);
@@ -302,7 +302,7 @@
SurfaceControl.Transaction targetTransaction = spy(new StubTransaction());
SurfaceSyncGroup.setTransactionFactory(() -> targetTransaction);
- SyncTarget syncTarget = new SyncTarget();
+ SurfaceSyncGroup syncTarget = new SurfaceSyncGroup("FakeSyncTarget");
assertTrue(
syncGroup.add(syncTarget.mISurfaceSyncGroup, true /* parentSyncGroupMerge */,
null /* runnable */));
@@ -329,7 +329,7 @@
SurfaceControl.Transaction targetTransaction = spy(new StubTransaction());
SurfaceSyncGroup.setTransactionFactory(() -> targetTransaction);
- SyncTarget syncTarget = new SyncTarget();
+ SurfaceSyncGroup syncTarget = new SurfaceSyncGroup("FakeSyncTarget");
assertTrue(syncGroup.add(syncTarget, null /* runnable */));
syncTarget.markSyncReady();
@@ -344,13 +344,13 @@
final CountDownLatch finishedLatch = new CountDownLatch(1);
SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
- SyncTarget syncTarget = new SyncTarget();
+ SurfaceSyncGroup syncTarget = new SurfaceSyncGroup("FakeSyncTarget");
syncGroup.add(syncTarget, null /* runnable */);
// Add the syncTarget to the same syncGroup and ensure it doesn't crash.
syncGroup.add(syncTarget, null /* runnable */);
syncGroup.markSyncReady();
- syncTarget.onBufferReady();
+ syncTarget.markSyncReady();
try {
finishedLatch.await(5, TimeUnit.SECONDS);
@@ -359,14 +359,4 @@
}
assertEquals(0, finishedLatch.getCount());
}
-
- private static class SyncTarget extends SurfaceSyncGroup {
- SyncTarget() {
- super("FakeSyncTarget");
- }
-
- void onBufferReady() {
- markSyncReady();
- }
- }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index e7813ff..2bfc5ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -56,6 +56,7 @@
import android.platform.test.annotations.Presubmit;
import android.view.Gravity;
import android.view.InsetsState;
+import android.view.WindowInsets;
import androidx.test.filters.SmallTest;
@@ -1918,16 +1919,20 @@
state.setDisplayFrame(displayFrame);
if (sl > dl) {
- state.getSource(ITYPE_CLIMATE_BAR).setFrame(dl, dt, sl, db);
+ state.getOrCreateSource(ITYPE_CLIMATE_BAR, WindowInsets.Type.statusBars())
+ .setFrame(dl, dt, sl, db);
}
if (st > dt) {
- state.getSource(ITYPE_STATUS_BAR).setFrame(dl, dt, dr, st);
+ state.getOrCreateSource(ITYPE_STATUS_BAR, WindowInsets.Type.statusBars())
+ .setFrame(dl, dt, dr, st);
}
if (sr < dr) {
- state.getSource(ITYPE_EXTRA_NAVIGATION_BAR).setFrame(sr, dt, dr, db);
+ state.getOrCreateSource(ITYPE_EXTRA_NAVIGATION_BAR, WindowInsets.Type.navigationBars())
+ .setFrame(sr, dt, dr, db);
}
if (sb < db) {
- state.getSource(ITYPE_NAVIGATION_BAR).setFrame(dl, sb, dr, db);
+ state.getOrCreateSource(ITYPE_NAVIGATION_BAR, WindowInsets.Type.navigationBars())
+ .setFrame(dl, sb, dr, db);
}
// Recompute config and push to children.
display.onRequestedOverrideConfigurationChanged(display.getConfiguration());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
index 19da718..1e5ec4c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.statusBars;
@@ -47,7 +47,7 @@
private InsetsSource mSource = new InsetsSource(ITYPE_STATUS_BAR, statusBars());
private WindowContainerInsetsSourceProvider mProvider;
- private InsetsSource mImeSource = new InsetsSource(ITYPE_IME, ime());
+ private InsetsSource mImeSource = new InsetsSource(ID_IME, ime());
private WindowContainerInsetsSourceProvider mImeProvider;
@Before
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
index 56c59cc..731a235 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
@@ -16,12 +16,13 @@
package com.android.server.wm;
-import static android.view.InsetsState.ITYPE_BOTTOM_DISPLAY_CUTOUT;
-import static android.view.InsetsState.ITYPE_LEFT_DISPLAY_CUTOUT;
+import static android.view.InsetsSource.ID_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_RIGHT_DISPLAY_CUTOUT;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.ITYPE_TOP_DISPLAY_CUTOUT;
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowLayout.UNSPECIFIED_LENGTH;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
@@ -40,6 +41,7 @@
import android.platform.test.annotations.Presubmit;
import android.view.DisplayCutout;
import android.view.Gravity;
+import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.WindowInsets;
import android.view.WindowLayout;
@@ -88,9 +90,9 @@
mAttrs = new WindowManager.LayoutParams();
mState = new InsetsState();
mState.setDisplayFrame(new Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
- mState.getSource(ITYPE_STATUS_BAR).setFrame(
+ mState.getOrCreateSource(ITYPE_STATUS_BAR, statusBars()).setFrame(
0, 0, DISPLAY_WIDTH, STATUS_BAR_HEIGHT);
- mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(
+ mState.getOrCreateSource(ITYPE_NAVIGATION_BAR, navigationBars()).setFrame(
0, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT);
mState.getDisplayCutoutSafe(mDisplayCutoutSafe);
mWindowBounds = new Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
@@ -117,14 +119,14 @@
new Rect(),
WATERFALL_INSETS));
mState.getDisplayCutoutSafe(mDisplayCutoutSafe);
- mState.getSource(ITYPE_LEFT_DISPLAY_CUTOUT).setFrame(
- 0, 0, mDisplayCutoutSafe.left, DISPLAY_HEIGHT);
- mState.getSource(ITYPE_TOP_DISPLAY_CUTOUT).setFrame(
- 0, 0, DISPLAY_WIDTH, mDisplayCutoutSafe.top);
- mState.getSource(ITYPE_RIGHT_DISPLAY_CUTOUT).setFrame(
- mDisplayCutoutSafe.right, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
- mState.getSource(ITYPE_BOTTOM_DISPLAY_CUTOUT).setFrame(
- 0, mDisplayCutoutSafe.bottom, DISPLAY_WIDTH, DISPLAY_HEIGHT);
+ mState.getOrCreateSource(InsetsSource.createId(null, 0, displayCutout()), displayCutout())
+ .setFrame(0, 0, mDisplayCutoutSafe.left, DISPLAY_HEIGHT);
+ mState.getOrCreateSource(InsetsSource.createId(null, 1, displayCutout()), displayCutout())
+ .setFrame(0, 0, DISPLAY_WIDTH, mDisplayCutoutSafe.top);
+ mState.getOrCreateSource(InsetsSource.createId(null, 2, displayCutout()), displayCutout())
+ .setFrame(mDisplayCutoutSafe.right, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
+ mState.getOrCreateSource(InsetsSource.createId(null, 3, displayCutout()), displayCutout())
+ .setFrame(0, mDisplayCutoutSafe.bottom, DISPLAY_WIDTH, DISPLAY_HEIGHT);
}
private static void assertInsetByTopBottom(int top, int bottom, Rect actual) {
@@ -266,8 +268,8 @@
@Test
public void fitInvisibleInsets() {
- mState.getSource(ITYPE_STATUS_BAR).setVisible(false);
- mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(false);
+ mState.setSourceVisible(ITYPE_STATUS_BAR, false);
+ mState.setSourceVisible(ITYPE_NAVIGATION_BAR, false);
computeFrames();
assertInsetByTopBottom(0, 0, mFrames.displayFrame);
@@ -277,8 +279,8 @@
@Test
public void fitInvisibleInsetsIgnoringVisibility() {
- mState.getSource(ITYPE_STATUS_BAR).setVisible(false);
- mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(false);
+ mState.setSourceVisible(ITYPE_STATUS_BAR, false);
+ mState.setSourceVisible(ITYPE_NAVIGATION_BAR, false);
mAttrs.setFitInsetsIgnoringVisibility(true);
computeFrames();
@@ -289,9 +291,9 @@
@Test
public void insetParentFrameByIme() {
- mState.getSource(InsetsState.ITYPE_IME).setVisible(true);
- mState.getSource(InsetsState.ITYPE_IME).setFrame(
- 0, DISPLAY_HEIGHT - IME_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT);
+ mState.getOrCreateSource(ID_IME, ime())
+ .setVisible(true)
+ .setFrame(0, DISPLAY_HEIGHT - IME_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT);
mAttrs.privateFlags |= PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
computeFrames();
@@ -363,8 +365,8 @@
@Test
public void layoutInDisplayCutoutModeDefaultWithInvisibleSystemBars() {
addDisplayCutout();
- mState.getSource(ITYPE_STATUS_BAR).setVisible(false);
- mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(false);
+ mState.setSourceVisible(ITYPE_STATUS_BAR, false);
+ mState.setSourceVisible(ITYPE_NAVIGATION_BAR, false);
mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
mAttrs.setFitInsetsTypes(0);
computeFrames();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index d3e5a8a..c44869b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -20,7 +20,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.Surface.ROTATION_0;
@@ -1039,12 +1039,16 @@
mAppWindow.mAboveInsetsState.addSource(navSource);
navSource.setVisible(false);
- assertTrue(mImeWindow.getInsetsState().getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR));
- assertFalse(mAppWindow.getInsetsState().getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR));
+ assertTrue(mImeWindow.getInsetsState().isSourceOrDefaultVisible(
+ ITYPE_NAVIGATION_BAR, navigationBars()));
+ assertFalse(mAppWindow.getInsetsState().isSourceOrDefaultVisible(
+ ITYPE_NAVIGATION_BAR, navigationBars()));
navSource.setVisible(true);
- assertTrue(mImeWindow.getInsetsState().getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR));
- assertTrue(mAppWindow.getInsetsState().getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR));
+ assertTrue(mImeWindow.getInsetsState().isSourceOrDefaultVisible(
+ ITYPE_NAVIGATION_BAR, navigationBars()));
+ assertTrue(mAppWindow.getInsetsState().isSourceOrDefaultVisible(
+ ITYPE_NAVIGATION_BAR, navigationBars()));
}
@Test
@@ -1069,8 +1073,8 @@
controller.updateAboveInsetsState(false);
// Expect all app windows behind IME can receive IME insets visible.
- assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
- assertTrue(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
+ assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+ assertTrue(app2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
// Simulate app plays closing transition to app2.
app.mActivityRecord.commitVisibility(false, false);
@@ -1078,8 +1082,8 @@
assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
// Verify the IME insets is visible on app, but not for app2 during app task switching.
- assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
- assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
+ assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+ assertFalse(app2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
}
@Test
@@ -1108,8 +1112,8 @@
// Expect app windows behind IME can receive IME insets visible,
// but not for app2 in background.
- assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
- assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
+ assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+ assertFalse(app2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
// Simulate app plays closing transition to app2.
// And app2 is now IME layering target but not yet to be the IME input target.
@@ -1119,8 +1123,8 @@
assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
// Verify the IME insets is still visible on app, but not for app2 during task switching.
- assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
- assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
+ assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+ assertFalse(app2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
}
@SetupWindows(addWindows = W_ACTIVITY)
@@ -1165,18 +1169,18 @@
mNotificationShadeWindow.setHasSurface(true);
mNotificationShadeWindow.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE;
assertTrue(mNotificationShadeWindow.canBeImeTarget());
- mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindowContainer(
+ mDisplayContent.getInsetsStateController().getSourceProvider(ID_IME).setWindowContainer(
mImeWindow, null, null);
mDisplayContent.computeImeTarget(true);
assertEquals(mNotificationShadeWindow, mDisplayContent.getImeTarget(IME_TARGET_LAYERING));
mDisplayContent.getInsetsStateController().getRawInsetsState()
- .setSourceVisible(ITYPE_IME, true);
+ .setSourceVisible(ID_IME, true);
// Verify notificationShade can still get IME insets even windowing mode is multi-window.
InsetsState state = mNotificationShadeWindow.getInsetsState();
- assertNotNull(state.peekSource(ITYPE_IME));
- assertTrue(state.getSource(ITYPE_IME).isVisible());
+ assertNotNull(state.peekSource(ID_IME));
+ assertTrue(state.isSourceOrDefaultVisible(ID_IME, ime()));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index 3ff791b..bd63560 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -262,7 +262,7 @@
@Test
public void testSetInsetsFrozen_notAffectImeWindowState() {
// Pre-condition: make the IME window be controlled by IME insets provider.
- mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindowContainer(
+ mDisplayContent.getInsetsStateController().getSourceProvider(ID_IME).setWindowContainer(
mDisplayContent.mInputMethodWindow, null, null);
// Simulate an app window to be the IME layering target, assume the app window has no
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 17c354a..aa1d556 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -23,7 +23,9 @@
import android.media.IAudioService;
import android.media.midi.MidiDeviceInfo;
import android.os.Bundle;
+import android.os.FileObserver;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.provider.Settings;
import android.service.usb.UsbAlsaManagerProto;
import android.util.Slog;
@@ -34,9 +36,11 @@
import libcore.io.IoUtils;
+import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
/**
@@ -52,6 +56,11 @@
private static final String ALSA_DIRECTORY = "/dev/snd/";
+ private static final int ALSA_DEVICE_TYPE_UNKNOWN = 0;
+ private static final int ALSA_DEVICE_TYPE_PLAYBACK = 1;
+ private static final int ALSA_DEVICE_TYPE_CAPTURE = 2;
+ private static final int ALSA_DEVICE_TYPE_MIDI = 3;
+
private final Context mContext;
private IAudioService mAudioService;
private final boolean mHasMidiFeature;
@@ -110,11 +119,26 @@
/**
* List of connected MIDI devices
*/
- private final HashMap<String, UsbMidiDevice>
- mMidiDevices = new HashMap<String, UsbMidiDevice>();
+ private final HashMap<String, UsbAlsaMidiDevice>
+ mMidiDevices = new HashMap<String, UsbAlsaMidiDevice>();
- // UsbMidiDevice for USB peripheral mode (gadget) device
- private UsbMidiDevice mPeripheralMidiDevice = null;
+ // UsbAlsaMidiDevice for USB peripheral mode (gadget) device
+ private UsbAlsaMidiDevice mPeripheralMidiDevice = null;
+
+ private final HashSet<Integer> mAlsaCards = new HashSet<>();
+ private final FileObserver mAlsaObserver = new FileObserver(new File(ALSA_DIRECTORY),
+ FileObserver.CREATE | FileObserver.DELETE) {
+ public void onEvent(int event, String path) {
+ switch (event) {
+ case FileObserver.CREATE:
+ alsaFileAdded(path);
+ break;
+ case FileObserver.DELETE:
+ alsaFileRemoved(path);
+ break;
+ }
+ }
+ };
/* package */ UsbAlsaManager(Context context) {
mContext = context;
@@ -124,6 +148,7 @@
public void systemReady() {
mAudioService = IAudioService.Stub.asInterface(
ServiceManager.getService(Context.AUDIO_SERVICE));
+ mAlsaObserver.startWatching();
}
/**
@@ -224,6 +249,8 @@
return;
}
+ waitForAlsaDevice(cardRec.getCardNum(), true /*isAdded*/);
+
// Add it to the devices list
boolean hasInput = parser.hasInput()
&& !isDeviceDenylisted(usbDevice.getVendorId(), usbDevice.getProductId(),
@@ -304,11 +331,11 @@
Slog.d(TAG, "numLegacyMidiOutputs:" + numLegacyMidiOutputs);
}
- UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, properties,
+ UsbAlsaMidiDevice midiDevice = UsbAlsaMidiDevice.create(mContext, properties,
cardRec.getCardNum(), 0 /*device*/, numLegacyMidiInputs,
numLegacyMidiOutputs);
- if (usbMidiDevice != null) {
- mMidiDevices.put(deviceAddress, usbMidiDevice);
+ if (midiDevice != null) {
+ mMidiDevices.put(deviceAddress, midiDevice);
}
}
}
@@ -322,15 +349,16 @@
UsbAlsaDevice alsaDevice = removeAlsaDeviceFromList(deviceAddress);
Slog.i(TAG, "USB Audio Device Removed: " + alsaDevice);
if (alsaDevice != null && alsaDevice == mSelectedDevice) {
+ waitForAlsaDevice(alsaDevice.getCardNum(), false /*isAdded*/);
deselectAlsaDevice();
selectDefaultDevice(); // if there any external devices left, select one of them
}
// MIDI
- UsbMidiDevice usbMidiDevice = mMidiDevices.remove(deviceAddress);
- if (usbMidiDevice != null) {
+ UsbAlsaMidiDevice midiDevice = mMidiDevices.remove(deviceAddress);
+ if (midiDevice != null) {
Slog.i(TAG, "USB MIDI Device Removed: " + deviceAddress);
- IoUtils.closeQuietly(usbMidiDevice);
+ IoUtils.closeQuietly(midiDevice);
}
logDevices("usbDeviceRemoved()");
@@ -353,7 +381,7 @@
com.android.internal.R.string.usb_midi_peripheral_product_name));
properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card);
properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device);
- mPeripheralMidiDevice = UsbMidiDevice.create(mContext, properties, card, device,
+ mPeripheralMidiDevice = UsbAlsaMidiDevice.create(mContext, properties, card, device,
1 /* numInputs */, 1 /* numOutputs */);
} else if (!enabled && mPeripheralMidiDevice != null) {
IoUtils.closeQuietly(mPeripheralMidiDevice);
@@ -361,6 +389,89 @@
}
}
+ private boolean waitForAlsaDevice(int card, boolean isAdded) {
+ if (DEBUG) {
+ Slog.e(TAG, "waitForAlsaDevice(c:" + card + ")");
+ }
+
+ // This value was empirically determined.
+ final int kWaitTimeMs = 2500;
+
+ synchronized (mAlsaCards) {
+ long timeoutMs = SystemClock.elapsedRealtime() + kWaitTimeMs;
+ while ((isAdded ^ mAlsaCards.contains(card))
+ && timeoutMs > SystemClock.elapsedRealtime()) {
+ long waitTimeMs = timeoutMs - SystemClock.elapsedRealtime();
+ if (waitTimeMs > 0) {
+ try {
+ mAlsaCards.wait(waitTimeMs);
+ } catch (InterruptedException e) {
+ Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file.");
+ }
+ }
+ }
+ final boolean cardFound = mAlsaCards.contains(card);
+ if ((isAdded ^ cardFound) && timeoutMs > SystemClock.elapsedRealtime()) {
+ Slog.e(TAG, "waitForAlsaDevice(" + card + ") timeout");
+ } else {
+ Slog.i(TAG, "waitForAlsaDevice for device card=" + card + ", isAdded=" + isAdded
+ + ", found=" + cardFound);
+ }
+ return cardFound;
+ }
+ }
+
+ private int getCardNumberFromAlsaFilePath(String path) {
+ int type = ALSA_DEVICE_TYPE_UNKNOWN;
+ if (path.startsWith("pcmC")) {
+ if (path.endsWith("p")) {
+ type = ALSA_DEVICE_TYPE_PLAYBACK;
+ } else if (path.endsWith("c")) {
+ type = ALSA_DEVICE_TYPE_CAPTURE;
+ }
+ } else if (path.startsWith("midiC")) {
+ type = ALSA_DEVICE_TYPE_MIDI;
+ }
+
+ if (type == ALSA_DEVICE_TYPE_UNKNOWN) {
+ Slog.i(TAG, "Unknown type file(" + path + ") added.");
+ return -1;
+ }
+ try {
+ int c_index = path.indexOf('C');
+ int d_index = path.indexOf('D');
+ return Integer.parseInt(path.substring(c_index + 1, d_index));
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not parse ALSA file name " + path, e);
+ return -1;
+ }
+ }
+
+ private void alsaFileAdded(String path) {
+ Slog.i(TAG, "alsaFileAdded(" + path + ")");
+ final int card = getCardNumberFromAlsaFilePath(path);
+ if (card == -1) {
+ return;
+ }
+ synchronized (mAlsaCards) {
+ if (!mAlsaCards.contains(card)) {
+ Slog.d(TAG, "Adding ALSA device card=" + card);
+ mAlsaCards.add(card);
+ mAlsaCards.notifyAll();
+ }
+ }
+ }
+
+ private void alsaFileRemoved(String path) {
+ final int card = getCardNumberFromAlsaFilePath(path);
+ if (card == -1) {
+ return;
+ }
+ synchronized (mAlsaCards) {
+ mAlsaCards.remove(card);
+ }
+ }
+
//
// Devices List
//
@@ -389,9 +500,9 @@
}
for (String deviceAddr : mMidiDevices.keySet()) {
- // A UsbMidiDevice does not have a handle to the UsbDevice anymore
- mMidiDevices.get(deviceAddr).dump(deviceAddr, dump, "midi_devices",
- UsbAlsaManagerProto.MIDI_DEVICES);
+ // A UsbAlsaMidiDevice does not have a handle to the UsbDevice anymore
+ mMidiDevices.get(deviceAddr).dump(deviceAddr, dump, "alsa_midi_devices",
+ UsbAlsaManagerProto.ALSA_MIDI_DEVICES);
}
dump.end(token);
diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbAlsaMidiDevice.java
similarity index 91%
rename from services/usb/java/com/android/server/usb/UsbMidiDevice.java
rename to services/usb/java/com/android/server/usb/UsbAlsaMidiDevice.java
index d9ad703..e92c034 100644
--- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaMidiDevice.java
@@ -10,7 +10,7 @@
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions an
+ * See the License for the specific language governing permissions and
* limitations under the License.
*/
@@ -24,7 +24,7 @@
import android.media.midi.MidiManager;
import android.media.midi.MidiReceiver;
import android.os.Bundle;
-import android.service.usb.UsbMidiDeviceProto;
+import android.service.usb.UsbAlsaMidiDeviceProto;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -43,8 +43,12 @@
import java.io.FileOutputStream;
import java.io.IOException;
-public final class UsbMidiDevice implements Closeable {
- private static final String TAG = "UsbMidiDevice";
+/**
+ * Opens device connections to MIDI 1.0 endpoints.
+ * These endpoints will use ALSA.
+ */
+public final class UsbAlsaMidiDevice implements Closeable {
+ private static final String TAG = "UsbAlsaMidiDevice";
private final int mAlsaCard;
private final int mAlsaDevice;
@@ -142,13 +146,13 @@
}
/**
- * Creates an UsbMidiDevice based on the input parameters. Read/Write streams
+ * Creates an UsbAlsaMidiDevice based on the input parameters. Read/Write streams
* will be created individually as some devices don't have the same number of
* inputs and outputs.
*/
- public static UsbMidiDevice create(Context context, Bundle properties, int card,
+ public static UsbAlsaMidiDevice create(Context context, Bundle properties, int card,
int device, int numInputs, int numOutputs) {
- UsbMidiDevice midiDevice = new UsbMidiDevice(card, device, numInputs, numOutputs);
+ UsbAlsaMidiDevice midiDevice = new UsbAlsaMidiDevice(card, device, numInputs, numOutputs);
if (!midiDevice.register(context, properties)) {
IoUtils.closeQuietly(midiDevice);
Log.e(TAG, "createDeviceServer failed");
@@ -157,7 +161,7 @@
return midiDevice;
}
- private UsbMidiDevice(int card, int device, int numInputs, int numOutputs) {
+ private UsbAlsaMidiDevice(int card, int device, int numInputs, int numOutputs) {
mAlsaCard = card;
mAlsaDevice = device;
mNumInputs = numInputs;
@@ -217,7 +221,7 @@
if (inputStreamCount > 0) {
// Create input thread which will read from all output ports of the physical device
- new Thread("UsbMidiDevice input thread") {
+ new Thread("UsbAlsaMidiDevice input thread") {
@Override
public void run() {
byte[] buffer = new byte[BUFFER_SIZE];
@@ -272,13 +276,13 @@
final FileOutputStream outputStreamF = mOutputStreams[port];
final int portF = port;
- new Thread("UsbMidiDevice output thread " + port) {
+ new Thread("UsbAlsaMidiDevice output thread " + port) {
@Override
public void run() {
while (true) {
MidiEvent event;
try {
- event = (MidiEvent)eventSchedulerF.waitNextEvent();
+ event = (MidiEvent) eventSchedulerF.waitNextEvent();
} catch (InterruptedException e) {
// try again
continue;
@@ -303,9 +307,9 @@
}
private boolean register(Context context, Bundle properties) {
- MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
+ MidiManager midiManager = context.getSystemService(MidiManager.class);
if (midiManager == null) {
- Log.e(TAG, "No MidiManager in UsbMidiDevice.register()");
+ Log.e(TAG, "No MidiManager in UsbAlsaMidiDevice.register()");
return false;
}
@@ -365,9 +369,9 @@
long id) {
long token = dump.start(idName, id);
- dump.write("device_address", UsbMidiDeviceProto.DEVICE_ADDRESS, deviceAddr);
- dump.write("card", UsbMidiDeviceProto.CARD, mAlsaCard);
- dump.write("device", UsbMidiDeviceProto.DEVICE, mAlsaDevice);
+ dump.write("device_address", UsbAlsaMidiDeviceProto.DEVICE_ADDRESS, deviceAddr);
+ dump.write("card", UsbAlsaMidiDeviceProto.CARD, mAlsaCard);
+ dump.write("device", UsbAlsaMidiDeviceProto.DEVICE, mAlsaDevice);
dump.end(token);
}
diff --git a/services/voiceinteraction/Android.bp b/services/voiceinteraction/Android.bp
index af0eca9..7332d2d 100644
--- a/services/voiceinteraction/Android.bp
+++ b/services/voiceinteraction/Android.bp
@@ -20,6 +20,7 @@
srcs: [":services.voiceinteraction-sources"],
libs: [
"services.core",
+ "app-compat-annotations",
"android.hardware.power-V1-java",
"android.hardware.power-V1.0-java",
],
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 81717f4..7824c69 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -298,35 +298,33 @@
}
@Override
- public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback,
+ public int startRecognition(GenericSoundModel soundModel,
+ IRecognitionStatusCallback callback,
RecognitionConfig config, boolean runInBatterySaverMode) {
try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+
+ if (soundModel == null) {
+ Slog.e(TAG, "Null model passed to startRecognition");
+ return STATUS_ERROR;
+ }
+
if (runInBatterySaverMode) {
enforceCallingPermission(Manifest.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER);
}
if (DEBUG) {
- Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
+ Slog.i(TAG, "startRecognition(): Uuid : " + soundModel.toString());
}
sEventLogger.enqueue(new EventLogger.StringEvent(
- "startRecognition(): Uuid : " + parcelUuid));
+ "startRecognition(): Uuid : " + soundModel.getUuid().toString()));
- GenericSoundModel model = getSoundModel(parcelUuid);
- if (model == null) {
- Slog.w(TAG, "Null model in database for id: " + parcelUuid);
-
- sEventLogger.enqueue(new EventLogger.StringEvent(
- "startRecognition(): Null model in database for id: " + parcelUuid));
-
- return STATUS_ERROR;
- }
-
- int ret = mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model,
+ int ret = mSoundTriggerHelper.startGenericRecognition(soundModel.getUuid(),
+ soundModel,
callback, config, runInBatterySaverMode);
if (ret == STATUS_OK) {
- mSoundModelStatTracker.onStart(parcelUuid.getUuid());
+ mSoundModelStatTracker.onStart(soundModel.getUuid());
}
return ret;
}
@@ -379,8 +377,7 @@
sEventLogger.enqueue(new EventLogger.StringEvent("updateSoundModel(): model = "
+ soundModel));
-
- mDbHelper.updateGenericSoundModel(soundModel);
+ mDbHelper.updateGenericSoundModel(soundModel);
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 445446e..cc22847 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -51,7 +51,6 @@
import android.os.ServiceManager;
import android.os.SharedMemory;
import android.provider.DeviceConfig;
-import android.service.voice.HotwordDetectedResult;
import android.service.voice.HotwordDetectionService;
import android.service.voice.HotwordDetector;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
@@ -66,6 +65,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
+import com.android.internal.app.IVisualQueryDetectionAttentionListener;
import com.android.internal.infra.ServiceConnector;
import com.android.server.LocalServices;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
@@ -88,19 +88,18 @@
static final boolean DEBUG = false;
/**
- * For apps targeting Android API {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above,
- * implementors of the {@link HotwordDetectionService} must not augment the phrase IDs which are
- * supplied via {@link HotwordDetectionService
- * #onDetect(AlwaysOnHotwordDetector.EventPayload, long, HotwordDetectionService.Callback)}.
+ * For apps targeting Android API Build.VERSION_CODES#UPSIDE_DOWN_CAKE and above,
+ * implementors of the HotwordDetectionService must not augment the phrase IDs which are
+ * supplied via HotwordDetectionService
+ * #onDetect(AlwaysOnHotwordDetector.EventPayload, long, HotwordDetectionService.Callback).
*
- * <p>The {@link HotwordDetectedResult#getHotwordPhraseId()} must match one of the phrase IDs
- * from the {@link android.service.voice.AlwaysOnHotwordDetector
- * .EventPayload#getKeyphraseRecognitionExtras()} list.
+ * <p>The HotwordDetectedResult#getHotwordPhraseId() must match one of the phrase IDs
+ * from the AlwaysOnHotwordDetector.EventPayload#getKeyphraseRecognitionExtras() list.
* </p>
*
- * <p>This behavior change is made to ensure the {@link HotwordDetectionService} honors what
- * it receives from the {@link android.hardware.soundtrigger.SoundTriggerModule}, and it
- * cannot signal to the client application a phrase which was not origially detected.
+ * <p>This behavior change is made to ensure the HotwordDetectionService honors what
+ * it receives from the android.hardware.soundtrigger.SoundTriggerModule, and it
+ * cannot signal to the client application a phrase which was not originally detected.
* </p>
*/
@ChangeId
@@ -328,6 +327,15 @@
session.startListeningFromMicLocked(audioFormat, callback);
}
+ public void setVisualQueryDetectionAttentionListenerLocked(
+ @Nullable IVisualQueryDetectionAttentionListener listener) {
+ final VisualQueryDetectorSession session = getVisualQueryDetectorSessionLocked();
+ if (session == null) {
+ return;
+ }
+ session.setVisualQueryDetectionAttentionListenerLocked(listener);
+ }
+
/**
* This method is only used by VisualQueryDetector.
*/
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
index 621c3de..33150d8 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
@@ -33,6 +33,7 @@
import android.util.Slog;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
+import com.android.internal.app.IVisualQueryDetectionAttentionListener;
import java.io.PrintWriter;
import java.util.Objects;
@@ -49,6 +50,7 @@
final class VisualQueryDetectorSession extends DetectorSession {
private static final String TAG = "VisualQueryDetectorSession";
+ private IVisualQueryDetectionAttentionListener mAttentionListener;
private boolean mEgressingData;
private boolean mQueryStreaming;
@@ -64,6 +66,7 @@
logging);
mEgressingData = false;
mQueryStreaming = false;
+ mAttentionListener = null;
}
@Override
@@ -74,6 +77,11 @@
//TODO(b/261783819): Starts detection in VisualQueryDetectionService.
}
+ void setVisualQueryDetectionAttentionListenerLocked(
+ @Nullable IVisualQueryDetectionAttentionListener listener) {
+ mAttentionListener = listener;
+ }
+
@SuppressWarnings("GuardedBy")
void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
if (DEBUG) {
@@ -86,15 +94,31 @@
@Override
public void onAttentionGained() {
Slog.v(TAG, "BinderCallback#onAttentionGained");
- //TODO check to see if there is an active SysUI listener registered
mEgressingData = true;
+ if (mAttentionListener == null) {
+ return;
+ }
+ try {
+ mAttentionListener.onAttentionGained();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error delivering attention gained event.", e);
+ return;
+ }
}
@Override
public void onAttentionLost() {
Slog.v(TAG, "BinderCallback#onAttentionLost");
- //TODO check to see if there is an active SysUI listener registered
mEgressingData = false;
+ if (mAttentionListener == null) {
+ return;
+ }
+ try {
+ mAttentionListener.onAttentionLost();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error delivering attention lost event.", e);
+ return;
+ }
}
@Override
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 38bf9c2..0abed0b 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -87,6 +87,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
+import com.android.internal.app.IVisualQueryDetectionAttentionListener;
import com.android.internal.app.IVoiceActionCheckCallback;
import com.android.internal.app.IVoiceInteractionManagerService;
import com.android.internal.app.IVoiceInteractionSessionListener;
@@ -1338,6 +1339,39 @@
}
}
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
+ @Override
+ public void enableVisualQueryDetection(
+ IVisualQueryDetectionAttentionListener listener) {
+ super.enableVisualQueryDetection_enforcePermission();
+ synchronized (this) {
+
+ if (mImpl == null) {
+ Slog.w(TAG,
+ "enableVisualQueryDetection without running voice interaction service");
+ return;
+ }
+ this.mImpl.setVisualQueryDetectionAttentionListenerLocked(listener);
+ }
+ }
+
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
+ @Override
+ public void disableVisualQueryDetection() {
+ super.disableVisualQueryDetection_enforcePermission();
+ synchronized (this) {
+ if (mImpl == null) {
+ Slog.w(TAG,
+ "disableVisualQueryDetection without running voice interaction"
+ + " service");
+ return;
+ }
+ this.mImpl.setVisualQueryDetectionAttentionListenerLocked(null);
+ }
+ }
+
@Override
public void startPerceiving(
IVisualQueryDetectionVoiceInteractionCallback callback)
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 127195c..3dbd269 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -72,7 +72,9 @@
import android.util.Slog;
import android.view.IWindowManager;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
+import com.android.internal.app.IVisualQueryDetectionAttentionListener;
import com.android.internal.app.IVoiceActionCheckCallback;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractor;
@@ -247,6 +249,7 @@
Context.RECEIVER_EXPORTED);
}
+ @GuardedBy("this")
public void grantImplicitAccessLocked(int grantRecipientUid, @Nullable Intent intent) {
final int grantRecipientAppId = UserHandle.getAppId(grantRecipientUid);
final int grantRecipientUserId = UserHandle.getUserId(grantRecipientUid);
@@ -256,6 +259,7 @@
/* direct= */ true);
}
+ @GuardedBy("this")
public boolean showSessionLocked(@Nullable Bundle args, int flags,
@Nullable String attributionTag,
@Nullable IVoiceInteractionSessionShowCallback showCallback,
@@ -328,6 +332,7 @@
}
}
+ @GuardedBy("this")
public boolean hideSessionLocked() {
if (mActiveSession != null) {
return mActiveSession.hideLocked();
@@ -335,6 +340,7 @@
return false;
}
+ @GuardedBy("this")
public boolean deliverNewSessionLocked(IBinder token,
IVoiceInteractionSession session, IVoiceInteractor interactor) {
if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -345,6 +351,7 @@
return true;
}
+ @GuardedBy("this")
public int startVoiceActivityLocked(@Nullable String callingFeatureId, int callingPid,
int callingUid, IBinder token, Intent intent, String resolvedType) {
try {
@@ -367,6 +374,7 @@
}
}
+ @GuardedBy("this")
public int startAssistantActivityLocked(@Nullable String callingFeatureId, int callingPid,
int callingUid, IBinder token, Intent intent, String resolvedType) {
try {
@@ -389,6 +397,7 @@
}
}
+ @GuardedBy("this")
public void requestDirectActionsLocked(@NonNull IBinder token, int taskId,
@NonNull IBinder assistToken, @Nullable RemoteCallback cancellationCallback,
@NonNull RemoteCallback callback) {
@@ -444,6 +453,7 @@
}
}
+ @GuardedBy("this")
void performDirectActionLocked(@NonNull IBinder token, @NonNull String actionId,
@Nullable Bundle arguments, int taskId, IBinder assistToken,
@Nullable RemoteCallback cancellationCallback,
@@ -470,6 +480,7 @@
}
}
+ @GuardedBy("this")
public void setKeepAwakeLocked(IBinder token, boolean keepAwake) {
try {
if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -482,6 +493,7 @@
}
}
+ @GuardedBy("this")
public void closeSystemDialogsLocked(IBinder token) {
try {
if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -494,6 +506,7 @@
}
}
+ @GuardedBy("this")
public void finishLocked(IBinder token, boolean finishTask) {
if (mActiveSession == null || (!finishTask && token != mActiveSession.mToken)) {
Slog.w(TAG, "finish does not match active session");
@@ -503,6 +516,7 @@
mActiveSession = null;
}
+ @GuardedBy("this")
public void setDisabledShowContextLocked(int callingUid, int flags) {
int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
if (callingUid != activeUid) {
@@ -512,6 +526,7 @@
mDisabledShowContext = flags;
}
+ @GuardedBy("this")
public int getDisabledShowContextLocked(int callingUid) {
int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
if (callingUid != activeUid) {
@@ -521,6 +536,7 @@
return mDisabledShowContext;
}
+ @GuardedBy("this")
public int getUserDisabledShowContextLocked(int callingUid) {
int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
if (callingUid != activeUid) {
@@ -534,6 +550,7 @@
return mInfo.getSupportsLocalInteraction();
}
+ @GuardedBy("this")
public void startListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
if (DEBUG) {
Slog.d(TAG, "startListeningVisibleActivityChangedLocked: token=" + token);
@@ -546,6 +563,7 @@
mActiveSession.startListeningVisibleActivityChangedLocked();
}
+ @GuardedBy("this")
public void stopListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
if (DEBUG) {
Slog.d(TAG, "stopListeningVisibleActivityChangedLocked: token=" + token);
@@ -558,6 +576,7 @@
mActiveSession.stopListeningVisibleActivityChangedLocked();
}
+ @GuardedBy("this")
public void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) {
if (DEBUG) {
Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken);
@@ -572,6 +591,7 @@
mActiveSession.notifyActivityDestroyedLocked(activityToken);
}
+ @GuardedBy("this")
public void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) {
if (DEBUG) {
Slog.d(TAG, "notifyActivityEventChangedLocked type=" + type);
@@ -586,6 +606,7 @@
mActiveSession.notifyActivityEventChangedLocked(activityToken, type);
}
+ @GuardedBy("this")
public void updateStateLocked(
@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory,
@@ -606,6 +627,7 @@
}
}
+ @GuardedBy("this")
private void verifyDetectorForHotwordDetectionLocked(
@Nullable SharedMemory sharedMemory,
IHotwordRecognitionStatusCallback callback,
@@ -663,6 +685,7 @@
voiceInteractionServiceUid);
}
+ @GuardedBy("this")
private void verifyDetectorForVisualQueryDetectionLocked(@Nullable SharedMemory sharedMemory) {
Slog.v(TAG, "verifyDetectorForVisualQueryDetectionLocked");
@@ -701,6 +724,7 @@
}
}
+ @GuardedBy("this")
public void initAndVerifyDetectorLocked(
@NonNull Identity voiceInteractorIdentity,
@Nullable PersistableBundle options,
@@ -730,6 +754,7 @@
detectorType);
}
+ @GuardedBy("this")
public void destroyDetectorLocked(IBinder token) {
Slog.v(TAG, "destroyDetectorLocked");
@@ -748,6 +773,7 @@
}
}
+ @GuardedBy("this")
public void shutdownHotwordDetectionServiceLocked() {
if (DEBUG) {
Slog.d(TAG, "shutdownHotwordDetectionServiceLocked");
@@ -760,6 +786,16 @@
mHotwordDetectionConnection = null;
}
+ @GuardedBy("this")
+ public void setVisualQueryDetectionAttentionListenerLocked(
+ @Nullable IVisualQueryDetectionAttentionListener listener) {
+ if (mHotwordDetectionConnection == null) {
+ return;
+ }
+ mHotwordDetectionConnection.setVisualQueryDetectionAttentionListenerLocked(listener);
+ }
+
+ @GuardedBy("this")
public void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
if (DEBUG) {
Slog.d(TAG, "startPerceivingLocked");
@@ -773,6 +809,7 @@
mHotwordDetectionConnection.startPerceivingLocked(callback);
}
+ @GuardedBy("this")
public void stopPerceivingLocked() {
if (DEBUG) {
Slog.d(TAG, "stopPerceivingLocked");
@@ -786,6 +823,7 @@
mHotwordDetectionConnection.stopPerceivingLocked();
}
+ @GuardedBy("this")
public void startListeningFromMicLocked(
AudioFormat audioFormat,
IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
@@ -801,6 +839,7 @@
mHotwordDetectionConnection.startListeningFromMicLocked(audioFormat, callback);
}
+ @GuardedBy("this")
public void startListeningFromExternalSourceLocked(
ParcelFileDescriptor audioStream,
AudioFormat audioFormat,
@@ -825,6 +864,7 @@
options, token, callback);
}
+ @GuardedBy("this")
public void stopListeningFromMicLocked() {
if (DEBUG) {
Slog.d(TAG, "stopListeningFromMicLocked");
@@ -838,6 +878,7 @@
mHotwordDetectionConnection.stopListeningFromMicLocked();
}
+ @GuardedBy("this")
public void triggerHardwareRecognitionEventForTestLocked(
SoundTrigger.KeyphraseRecognitionEvent event,
IHotwordRecognitionStatusCallback callback) {
@@ -852,6 +893,7 @@
mHotwordDetectionConnection.triggerHardwareRecognitionEventForTestLocked(event, callback);
}
+ @GuardedBy("this")
public IRecognitionStatusCallback createSoundTriggerCallbackLocked(
IHotwordRecognitionStatusCallback callback) {
if (DEBUG) {
@@ -876,6 +918,7 @@
return null;
}
+ @GuardedBy("this")
boolean isIsolatedProcessLocked(@NonNull ServiceInfo serviceInfo) {
return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
&& (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
@@ -889,6 +932,7 @@
mHotwordDetectionConnection.forceRestart();
}
+ @GuardedBy("this")
void setDebugHotwordLoggingLocked(boolean logging) {
if (mHotwordDetectionConnection == null) {
Slog.w(TAG, "Failed to set temporary debug logging: no hotword detection active");
@@ -897,6 +941,7 @@
mHotwordDetectionConnection.setDebugHotwordLoggingLocked(logging);
}
+ @GuardedBy("this")
void resetHotwordDetectionConnectionLocked() {
if (DEBUG) {
Slog.d(TAG, "resetHotwordDetectionConnectionLocked");
@@ -911,6 +956,7 @@
mHotwordDetectionConnection = null;
}
+ @GuardedBy("this")
public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!mValid) {
pw.print(" NOT VALID: ");
@@ -947,6 +993,7 @@
}
}
+ @GuardedBy("this")
void startLocked() {
Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
intent.setComponent(mComponent);
@@ -971,6 +1018,7 @@
}
}
+ @GuardedBy("this")
void shutdownLocked() {
// If there is an active session, cancel it to allow it to clean up its window and other
// state.
@@ -998,6 +1046,7 @@
}
}
+ @GuardedBy("this")
void notifySoundModelsChangedLocked() {
if (mService == null) {
Slog.w(TAG, "Not bound to voice interaction service " + mComponent);
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index 315ac67..f3c91f6 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -28,6 +28,7 @@
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.text.TextUtils;
import com.android.internal.telecom.ClientTransactionalServiceRepository;
import com.android.internal.telecom.ICallControl;
@@ -67,7 +68,7 @@
/**
* @return the callId Telecom assigned to this CallControl object which should be attached to
- * an individual call.
+ * an individual call.
*/
@NonNull
public ParcelUuid getCallId() {
@@ -78,9 +79,9 @@
* Request Telecom set the call state to active.
*
* @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
- * will be called on.
+ * will be called on.
* @param callback that will be completed on the Telecom side that details success or failure
- * of the requested operation.
+ * of the requested operation.
*
* {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
* switched the call state to active
@@ -109,9 +110,9 @@
* but can be extended to setting a meeting to inactive.
*
* @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
- * will be called on.
+ * will be called on.
* @param callback that will be completed on the Telecom side that details success or failure
- * of the requested operation.
+ * of the requested operation.
*
* {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
* switched the call state to inactive
@@ -136,23 +137,42 @@
}
/**
- * Request Telecom set the call state to disconnect.
+ * Request Telecom disconnect the call and remove the call from telecom tracking.
*
- * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
- * will be called on.
- * @param callback that will be completed on the Telecom side that details success or failure
- * of the requested operation.
+ * @param disconnectCause represents the cause for disconnecting the call. The only valid
+ * codes for the {@link android.telecom.DisconnectCause} passed in are:
+ * <ul>
+ * <li>{@link DisconnectCause#LOCAL}</li>
+ * <li>{@link DisconnectCause#REMOTE}</li>
+ * <li>{@link DisconnectCause#REJECTED}</li>
+ * <li>{@link DisconnectCause#MISSED}</li>
+ * </ul>
*
- * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
- * disconnected the call.
+ * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+ * will be called on.
*
- * {@link OutcomeReceiver#onError} will be called if Telecom has failed to
- * disconnect the call. A {@link CallException} will be passed
- * that details why the operation failed.
+ * @param callback That will be completed on the Telecom side that details success or
+ * failure of the requested operation.
+ *
+ * {@link OutcomeReceiver#onResult} will be called if Telecom has
+ * successfully disconnected the call.
+ *
+ * {@link OutcomeReceiver#onError} will be called if Telecom has failed
+ * to disconnect the call. A {@link CallException} will be passed
+ * that details why the operation failed.
+ *
+ * <p>
+ * Note: After the call has been successfully disconnected, calling any CallControl API will
+ * result in the {@link OutcomeReceiver#onError} with
+ * {@link CallException#CODE_CALL_IS_NOT_BEING_TRACKED}.
*/
public void disconnect(@NonNull DisconnectCause disconnectCause,
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<Void, CallException> callback) {
+ Objects.requireNonNull(disconnectCause);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ validateDisconnectCause(disconnectCause);
if (mServerInterface != null) {
try {
mServerInterface.disconnect(mCallId, disconnectCause,
@@ -166,35 +186,6 @@
}
/**
- * Request Telecom reject the incoming call.
- *
- * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
- * will be called on.
- * @param callback that will be completed on the Telecom side that details success or failure
- * of the requested operation.
- *
- * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
- * rejected the incoming call.
- *
- * {@link OutcomeReceiver#onError} will be called if Telecom has failed to
- * reject the incoming call. A {@link CallException} will be passed
- * that details why the operation failed.
- */
- public void rejectCall(@CallbackExecutor @NonNull Executor executor,
- @NonNull OutcomeReceiver<Void, CallException> callback) {
- if (mServerInterface != null) {
- try {
- mServerInterface.rejectCall(mCallId,
- new CallControlResultReceiver("rejectCall", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
- }
- }
-
- /**
* Request start a call streaming session. On receiving valid request, telecom will bind to
* the {@link CallStreamingService} implemented by a general call streaming sender. So that the
* call streaming sender can perform streaming local device audio to another remote device and
@@ -231,10 +222,10 @@
* requesting a change. Instead, the new endpoint should be one of the valid endpoints provided
* by {@link CallEventCallback#onAvailableCallEndpointsChanged(List)}.
*
- * @param callEndpoint ; The {@link CallEndpoint} to change to.
- * @param executor ; The {@link Executor} on which the {@link OutcomeReceiver} callback
+ * @param callEndpoint The {@link CallEndpoint} to change to.
+ * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
* will be called on.
- * @param callback ; The {@link OutcomeReceiver} that will be completed on the Telecom side
+ * @param callback The {@link OutcomeReceiver} that will be completed on the Telecom side
* that details success or failure of the requested operation.
*
* {@link OutcomeReceiver#onResult} will be called if Telecom has
@@ -266,7 +257,9 @@
* Since {@link OutcomeReceiver}s cannot be passed via AIDL, a ResultReceiver (which can) must
* wrap the Clients {@link OutcomeReceiver} passed in and await for the Telecom Server side
* response in {@link ResultReceiver#onReceiveResult(int, Bundle)}.
- * @hide */
+ *
+ * @hide
+ */
private class CallControlResultReceiver extends ResultReceiver {
private final String mCallingMethod;
private final Executor mExecutor;
@@ -308,4 +301,18 @@
}
return new CallException(message, CallException.CODE_ERROR_UNKNOWN);
}
+
+ /** @hide */
+ private void validateDisconnectCause(DisconnectCause disconnectCause) {
+ final int code = disconnectCause.getCode();
+ if (code != DisconnectCause.LOCAL && code != DisconnectCause.REMOTE
+ && code != DisconnectCause.MISSED && code != DisconnectCause.REJECTED) {
+ throw new IllegalArgumentException(TextUtils.formatSimple(
+ "The DisconnectCause code provided, %d , is not a valid Disconnect code. Valid "
+ + "DisconnectCause codes are limited to [DisconnectCause.LOCAL, "
+ + "DisconnectCause.REMOTE, DisconnectCause.MISSED, or "
+ + "DisconnectCause.REJECTED]", disconnectCause.getCode()));
+ }
+ }
+
}
diff --git a/telecomm/java/android/telecom/CallException.java b/telecomm/java/android/telecom/CallException.java
index 0b0de6b..d191593 100644
--- a/telecomm/java/android/telecom/CallException.java
+++ b/telecomm/java/android/telecom/CallException.java
@@ -116,17 +116,6 @@
}
/**
- * Constructor for a new CallException when only message can be specified.
- * {@code CODE_ERROR_UNKNOWN} will be default code returned when calling {@code getCode}
- *
- * @param message related to why the exception was created
- */
- public CallException(@Nullable String message) {
- super(getMessage(message, CODE_ERROR_UNKNOWN));
- mMessage = message;
- }
-
- /**
* Constructor for a new CallException that has a defined error code in this class
*
* @param message related to why the exception was created
diff --git a/telecomm/java/com/android/internal/telecom/ICallControl.aidl b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
index a5c6e44..b78a77e 100644
--- a/telecomm/java/com/android/internal/telecom/ICallControl.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
@@ -28,7 +28,6 @@
void setActive(String callId, in ResultReceiver callback);
void setInactive(String callId, in ResultReceiver callback);
void disconnect(String callId, in DisconnectCause disconnectCause, in ResultReceiver callback);
- void rejectCall(String callId, in ResultReceiver callback);
void startCallStreaming(String callId, in ResultReceiver callback);
void requestCallEndpointChange(in CallEndpoint callEndpoint, in ResultReceiver callback);
}
\ No newline at end of file
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index d4a8600..8662359 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -20,6 +20,7 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -859,7 +860,7 @@
if ((subManager != null) &&
(!subManager.isSubscriptionAssociatedWithUser(subId, callerUserHandle))) {
// If subId is not associated with calling user, return false.
- Log.e(LOG_TAG,"User[User ID:" + callerUserHandle.getIdentifier()
+ Log.e(LOG_TAG, "User[User ID:" + callerUserHandle.getIdentifier()
+ "] is not associated with Subscription ID:" + subId);
return false;
@@ -869,4 +870,27 @@
}
return true;
}
+
+ /**
+ * Ensure the caller (or self, if not processing an IPC) has
+ * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE} or
+ * {@link android.Manifest.permission#READ_PHONE_NUMBERS}.
+ *
+ * @throws SecurityException if the caller does not have the required permission/privileges
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.READ_PHONE_NUMBERS,
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE
+ })
+ public static boolean checkCallingOrSelfReadPrivilegedPhoneStatePermissionOrReadPhoneNumber(
+ Context context, int subId, String callingPackage, @Nullable String callingFeatureId,
+ String message) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ return false;
+ }
+ return (context.checkCallingOrSelfPermission(
+ Manifest.permission.READ_PRIVILEGED_PHONE_STATE) == PERMISSION_GRANTED
+ || checkCallingOrSelfReadPhoneNumber(context, subId, callingPackage,
+ callingFeatureId, message));
+ }
}
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index 2c0087e..b59e855 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -17,10 +17,12 @@
import static android.telephony.euicc.EuiccCardManager.ResetOption;
+import android.Manifest;
import android.annotation.CallSuper;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.app.PendingIntent;
@@ -132,13 +134,25 @@
* @see android.telephony.euicc.EuiccManager#ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS
* The difference is this one is used by system to bring up the LUI.
*/
+ @RequiresPermission(Manifest.permission.BIND_EUICC_SERVICE)
public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS =
"android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
/** @see android.telephony.euicc.EuiccManager#ACTION_PROVISION_EMBEDDED_SUBSCRIPTION */
+ @RequiresPermission(Manifest.permission.BIND_EUICC_SERVICE)
public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION =
"android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
+ /** @see android.telephony.euicc.EuiccManager#ACTION_TRANSFER_EMBEDDED_SUBSCRIPTIONS */
+ @RequiresPermission(Manifest.permission.BIND_EUICC_SERVICE)
+ public static final String ACTION_TRANSFER_EMBEDDED_SUBSCRIPTIONS =
+ "android.service.euicc.action.TRANSFER_EMBEDDED_SUBSCRIPTIONS";
+
+ /** @see android.telephony.euicc.EuiccManager#ACTION_CONVERT_TO_EMBEDDED_SUBSCRIPTION */
+ @RequiresPermission(Manifest.permission.BIND_EUICC_SERVICE)
+ public static final String ACTION_CONVERT_TO_EMBEDDED_SUBSCRIPTION =
+ "android.service.euicc.action.CONVERT_TO_EMBEDDED_SUBSCRIPTION";
+
/**
* @see android.telephony.euicc.EuiccManager#ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED. This is
* a protected intent that can only be sent by the system, and requires the
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index f0f230f..acfc194 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3866,13 +3866,12 @@
* NR_SA - NR SA is unmetered for sub-6 frequencies
* NR_SA_MMWAVE - NR SA is unmetered for mmwave frequencies
*
- * Note that this config only applies if an unmetered SubscriptionPlan is set via
- * {@link SubscriptionManager#setSubscriptionPlans(int, List)} or an unmetered override is set
+ * Note that this config only applies if an unmetered SubscriptionPlan is set via {@link
+ * SubscriptionManager#setSubscriptionPlans(int, List, long)} or an unmetered override is set
* via {@link SubscriptionManager#setSubscriptionOverrideUnmetered(int, boolean, int[], long)}
* or {@link SubscriptionManager#setSubscriptionOverrideUnmetered(int, boolean, long)}.
* If neither SubscriptionPlans nor an override are set, then no network types can be unmetered
* regardless of the value of this config.
- * TODO: remove other unmetered keys and replace with this
* @hide
*/
public static final String KEY_UNMETERED_NETWORK_TYPES_STRING_ARRAY =
@@ -3887,73 +3886,18 @@
* NR_SA - NR SA is unmetered when roaming for sub-6 frequencies
* NR_SA_MMWAVE - NR SA is unmetered when roaming for mmwave frequencies
*
- * Note that this config only applies if an unmetered SubscriptionPlan is set via
- * {@link SubscriptionManager#setSubscriptionPlans(int, List)} or an unmetered override is set
+ * Note that this config only applies if an unmetered SubscriptionPlan is set via {@link
+ * SubscriptionManager#setSubscriptionPlans(int, List, long)} or an unmetered override is set
* via {@link SubscriptionManager#setSubscriptionOverrideUnmetered(int, boolean, int[], long)}
* or {@link SubscriptionManager#setSubscriptionOverrideUnmetered(int, boolean, long)}.
* If neither SubscriptionPlans nor an override are set, then no network types can be unmetered
* when roaming regardless of the value of this config.
- * TODO: remove KEY_UNMETERED_NR_NSA_WHEN_ROAMING_BOOL and replace with this
* @hide
*/
public static final String KEY_ROAMING_UNMETERED_NETWORK_TYPES_STRING_ARRAY =
"roaming_unmetered_network_types_string_array";
/**
- * Whether NR (non-standalone) should be unmetered for all frequencies.
- * If either {@link #KEY_UNMETERED_NR_NSA_MMWAVE_BOOL} or
- * {@link #KEY_UNMETERED_NR_NSA_SUB6_BOOL} are true, then this value will be ignored.
- * @hide
- */
- public static final String KEY_UNMETERED_NR_NSA_BOOL = "unmetered_nr_nsa_bool";
-
- /**
- * Whether NR (non-standalone) frequencies above 6GHz (millimeter wave) should be unmetered.
- * If this is true, then the value for {@link #KEY_UNMETERED_NR_NSA_BOOL} will be ignored.
- * @hide
- */
- public static final String KEY_UNMETERED_NR_NSA_MMWAVE_BOOL = "unmetered_nr_nsa_mmwave_bool";
-
- /**
- * Whether NR (non-standalone) frequencies below 6GHz (sub6) should be unmetered.
- * If this is true, then the value for {@link #KEY_UNMETERED_NR_NSA_BOOL} will be ignored.
- * @hide
- */
- public static final String KEY_UNMETERED_NR_NSA_SUB6_BOOL = "unmetered_nr_nsa_sub6_bool";
-
- /**
- * Whether NR (non-standalone) should be unmetered when the device is roaming.
- * If false, then the values for {@link #KEY_UNMETERED_NR_NSA_BOOL},
- * {@link #KEY_UNMETERED_NR_NSA_MMWAVE_BOOL}, {@link #KEY_UNMETERED_NR_NSA_SUB6_BOOL},
- * and unmetered {@link SubscriptionPlan} will be ignored.
- * @hide
- */
- public static final String KEY_UNMETERED_NR_NSA_WHEN_ROAMING_BOOL =
- "unmetered_nr_nsa_when_roaming_bool";
-
- /**
- * Whether NR (standalone) should be unmetered for all frequencies.
- * If either {@link #KEY_UNMETERED_NR_SA_MMWAVE_BOOL} or
- * {@link #KEY_UNMETERED_NR_SA_SUB6_BOOL} are true, then this value will be ignored.
- * @hide
- */
- public static final String KEY_UNMETERED_NR_SA_BOOL = "unmetered_nr_sa_bool";
-
- /**
- * Whether NR (standalone) frequencies above 6GHz (millimeter wave) should be unmetered.
- * If this is true, then the value for {@link #KEY_UNMETERED_NR_SA_BOOL} will be ignored.
- * @hide
- */
- public static final String KEY_UNMETERED_NR_SA_MMWAVE_BOOL = "unmetered_nr_sa_mmwave_bool";
-
- /**
- * Whether NR (standalone) frequencies below 6GHz (sub6) should be unmetered.
- * If this is true, then the value for {@link #KEY_UNMETERED_NR_SA_BOOL} will be ignored.
- * @hide
- */
- public static final String KEY_UNMETERED_NR_SA_SUB6_BOOL = "unmetered_nr_sa_sub6_bool";
-
- /**
* Support ASCII 7-BIT encoding for long SMS. This carrier config is used to enable
* this feature.
* @hide
@@ -10043,13 +9987,6 @@
sDefaults.putStringArray(KEY_UNMETERED_NETWORK_TYPES_STRING_ARRAY, new String[] {
"NR_NSA", "NR_NSA_MMWAVE", "NR_SA", "NR_SA_MMWAVE"});
sDefaults.putStringArray(KEY_ROAMING_UNMETERED_NETWORK_TYPES_STRING_ARRAY, new String[0]);
- sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_BOOL, false);
- sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_MMWAVE_BOOL, false);
- sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_SUB6_BOOL, false);
- sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_WHEN_ROAMING_BOOL, false);
- sDefaults.putBoolean(KEY_UNMETERED_NR_SA_BOOL, false);
- sDefaults.putBoolean(KEY_UNMETERED_NR_SA_MMWAVE_BOOL, false);
- sDefaults.putBoolean(KEY_UNMETERED_NR_SA_SUB6_BOOL, false);
sDefaults.putBoolean(KEY_ASCII_7_BIT_SUPPORT_FOR_LONG_MESSAGE_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_WIFI_CALLING_ICON_IN_STATUS_BAR_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL, false);
diff --git a/telephony/java/android/telephony/TelephonyFrameworkInitializer.java b/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
index 8308821..c2f5b8f 100644
--- a/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
+++ b/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
@@ -23,6 +23,7 @@
import android.telephony.euicc.EuiccCardManager;
import android.telephony.euicc.EuiccManager;
import android.telephony.ims.ImsManager;
+import android.telephony.satellite.SatelliteManager;
import com.android.internal.util.Preconditions;
@@ -98,6 +99,11 @@
context -> SmsManager.getSmsManagerForContextAndSubscriptionId(context,
SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)
);
+ SystemServiceRegistry.registerContextAwareService(
+ Context.SATELLITE_SERVICE,
+ SatelliteManager.class,
+ context -> new SatelliteManager(context)
+ );
}
/** @hide */
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 0921834..c238f24 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6000,6 +6000,7 @@
* Returns the IMS private user identity (IMPI) that was loaded from the ISIM.
* @return the IMPI, or null if not present or not loaded
* @hide
+ * @deprecated use {@link #getImsPrivateUserIdentity()}
*/
@UnsupportedAppUsage
public String getIsimImpi() {
@@ -6018,6 +6019,35 @@
}
/**
+ * Returns the IMS private user identity (IMPI) of the subscription that was loaded from the
+ * ISIM records {@link #APPTYPE_ISIM}. This value is fetched from the Elementary file EF_IMPI.
+ * The contents of the file is a <b>Ip Multimedia Service Private User Identity</b> of the user
+ * as defined in the section 4.2.2 of 3GPP TS 131 103.
+ *
+ * @return IMPI (IMS private user identity) of type string.
+ * @throws IllegalStateException in case the ISIM has’t been loaded
+ * @throws SecurityException if the caller does not have the required permission/privileges
+ * @hide
+ */
+ @NonNull
+ @RequiresPermission(android.Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
+ public String getImsPrivateUserIdentity() {
+ try {
+ IPhoneSubInfo info = getSubscriberInfoService();
+ if (info == null) {
+ Rlog.e(TAG, "getImsPrivateUserIdentity(): IPhoneSubInfo instance is NULL");
+ throw new RuntimeException("IMPI error: Subscriber Info is null");
+ }
+ return info.getImsPrivateUserIdentity(getSubId(), getOpPackageName(),
+ getAttributionTag());
+ } catch (RemoteException | NullPointerException | IllegalArgumentException ex) {
+ Rlog.e(TAG, "getImsPrivateUserIdentity() Exception = " + ex);
+ throw new RuntimeException(ex.getMessage());
+ }
+ }
+
+ /**
* Returns the IMS home network domain name that was loaded from the ISIM {@see #APPTYPE_ISIM}.
* @return the IMS domain name. Returns {@code null} if ISIM hasn't been loaded or IMS domain
* hasn't been loaded or isn't present on the ISIM.
@@ -6050,6 +6080,7 @@
* @return an array of IMPU strings, with one IMPU per string, or null if
* not present or not loaded
* @hide
+ * @deprecated use {@link #getImsPublicUserIdentities()}
*/
@UnsupportedAppUsage
@Nullable
@@ -6070,6 +6101,39 @@
}
/**
+ * Returns the IMS public user identities (IMPU) of the subscription that was loaded from the
+ * ISIM records {@link #APPTYPE_ISIM}. This value is fetched from the Elementary file EF_IMPU.
+ * The contents of the file are <b>Ip Multimedia Service Public User Identities</b> of the user
+ * as defined in the section 4.2.4 of 3GPP TS 131 103. It contains one or more records.
+ *
+ * @return List of public user identities of type android.net.Uri or empty list if
+ * EF_IMPU is not available.
+ * @throws IllegalStateException in case the ISIM hasn’t been loaded
+ * @throws SecurityException if the caller does not have the required permission/privilege
+ * @hide
+ */
+ @NonNull
+ @RequiresPermission(anyOf = {android.Manifest.permission.READ_PHONE_NUMBERS,
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE})
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
+ public List<Uri> getImsPublicUserIdentities() {
+ try {
+ IPhoneSubInfo info = getSubscriberInfoService();
+ if (info == null) {
+ throw new RuntimeException("IMPU error: Subscriber Info is null");
+ }
+ return info.getImsPublicUserIdentities(getSubId(), getOpPackageName(),
+ getAttributionTag());
+ } catch (IllegalArgumentException | NullPointerException ex) {
+ Rlog.e(TAG, "getImsPublicUserIdentities Exception = " + ex);
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "getImsPublicUserIdentities Exception = " + ex);
+ ex.rethrowAsRuntimeException();
+ }
+ return Collections.EMPTY_LIST;
+ }
+
+ /**
* Device call state: No activity.
*/
public static final int CALL_STATE_IDLE = 0;
@@ -9432,7 +9496,6 @@
ALLOWED_NETWORK_TYPES_REASON_POWER,
ALLOWED_NETWORK_TYPES_REASON_CARRIER,
ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G,
- ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface AllowedNetworkTypesReason {
@@ -9471,15 +9534,6 @@
public static final int ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G = 3;
/**
- * To indicate allowed network type change is requested by an update to the
- * {@link android.os.UserManager.DISALLOW_CELLULAR_2G} user restriction.
- *
- * @hide
- */
- @SystemApi
- public static final int ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS = 4;
-
- /**
* Set the allowed network types of the device and provide the reason triggering the allowed
* network change.
* <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or
@@ -9503,7 +9557,6 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE} permissions.
* <ol>
* <li>{@code TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G}</li>
- * <li>{@code TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS}</li>
* </ol>
*/
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -9578,7 +9631,6 @@
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;
}
return false;
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index cdb7d7c..1e21e9a9 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -82,6 +82,47 @@
public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS =
"android.telephony.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
+
+ /**
+ * Intent action to transfer an embedded subscriptions.
+ *
+ * <p> Action sent by apps (such as the Settings app) to the Telephony framework to transfer an
+ * embedded subscription.
+ *
+ * <p> Requires that the calling app has the
+ * {@code android.Manifest.permission#MODIFY_PHONE_STATE} permission.
+ *
+ * <p>The activity will immediately finish with {@link android.app.Activity#RESULT_CANCELED} if
+ * {@link #isEnabled} is false.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ public static final String ACTION_TRANSFER_EMBEDDED_SUBSCRIPTIONS =
+ "android.telephony.euicc.action.TRANSFER_EMBEDDED_SUBSCRIPTIONS";
+
+ /**
+ * Intent action to convert the physical subscription to an embedded subscription.
+ *
+ * <p> Action sent by apps (such as the Settings app) to the Telephony framework to convert
+ * physical sim to embedded sim.
+ *
+ * <p> Requires that the calling app has the
+ * {@code android.Manifest.permission#MODIFY_PHONE_STATE} permission.
+ *
+ * <p>The activity will immediately finish with {@link android.app.Activity#RESULT_CANCELED} if
+ * {@link #isEnabled} is false.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ public static final String ACTION_CONVERT_TO_EMBEDDED_SUBSCRIPTION =
+ "android.telephony.euicc.action.CONVERT_TO_EMBEDDED_SUBSCRIPTION";
+
/**
* Broadcast Action: The eUICC OTA status is changed.
* <p class="note">
diff --git a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
new file mode 100644
index 0000000..5c3fa32
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.telephony.satellite;
+
+import android.telephony.satellite.PointingInfo;
+
+/**
+ * Callback for position updates from the satellite service.
+ * @hide
+ */
+oneway interface ISatellitePositionUpdateCallback {
+ void onSatellitePositionUpdate(in PointingInfo pointingInfo);
+ void onMessageTransferStateUpdate(in int state);
+}
diff --git a/telephony/java/android/telephony/satellite/PointingInfo.aidl b/telephony/java/android/telephony/satellite/PointingInfo.aidl
new file mode 100644
index 0000000..7ff95cd
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/PointingInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+parcelable PointingInfo;
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
new file mode 100644
index 0000000..7a60c58
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyFrameworkInitializer;
+import android.util.ArrayMap;
+
+import com.android.internal.telephony.IIntegerConsumer;
+import com.android.internal.telephony.ITelephony;
+import com.android.telephony.Rlog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+/**
+ * Manages satellite operations such as provisioning, pointing, messaging, location sharing, etc.
+ * To get the object, call {@link Context#getSystemService(Context.SATELLITE_SERVICE)}.
+ * To create an instance of {@link SatelliteManager} associated with a specific subscription ID,
+ * call {@link #createForSubscriptionId(int)}.
+ *
+ * @hide
+ */
+@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SATELLITE)
+public class SatelliteManager {
+ private static final String TAG = "SatelliteManager";
+
+ /**
+ * Map of all SatellitePositionUpdateCallback and their associated callback ids.
+ */
+ private final Map<SatellitePositionUpdateCallback, Integer> mSatellitePositionUpdateCallbacks =
+ new ArrayMap<>();
+
+ /**
+ * AtomicInteger for the id of the next SatellitePositionUpdateCallback.
+ */
+ private final AtomicInteger mSatellitePositionUpdateCallbackId = new AtomicInteger(0);
+
+ /**
+ * The subscription ID for this SatelliteManager.
+ */
+ private final int mSubId;
+
+ /**
+ * Context this SatelliteManager is for.
+ */
+ @Nullable private final Context mContext;
+
+ /**
+ * Create an instance of the SatelliteManager.
+ *
+ * @param context The context the SatelliteManager belongs to.
+ */
+ public SatelliteManager(@Nullable Context context) {
+ // TODO: replace DEFAULT_SUBSCRIPTION_ID with DEFAULT_SATELLITE_SUBSCRIPTION_ID
+ this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
+ }
+
+ /**
+ * Create a new SatelliteManager associated with the given subscription ID.
+ *
+ * @param subId The subscription ID to create the SatelliteManager with.
+ * @return A SatelliteManager that uses the given subscription ID for all calls.
+ */
+ @NonNull public SatelliteManager createForSubscriptionId(int subId) {
+ return new SatelliteManager(mContext, subId);
+ }
+
+ /**
+ * Create an instance of the SatelliteManager associated with a particular subscription.
+ *
+ * @param context The context the SatelliteManager belongs to.
+ * @param subId The subscription ID associated with the SatelliteManager.
+ */
+ private SatelliteManager(@Nullable Context context, int subId) {
+ mContext = context;
+ mSubId = subId;
+ }
+
+ /**
+ * Successful response.
+ */
+ public static final int SATELLITE_SERVICE_SUCCESS = 0;
+ /**
+ * Satellite server is not reachable.
+ */
+ public static final int SATELLITE_SERVICE_SERVER_NOT_REACHABLE = 1;
+ /**
+ * Error received from the satellite server.
+ */
+ public static final int SATELLITE_SERVICE_SERVER_ERROR = 2;
+ /**
+ * Unexpected telephony internal error.
+ */
+ public static final int SATELLITE_SERVICE_TELEPHONY_INTERNAL_ERROR = 3;
+ /**
+ * Modem error received from the satellite service.
+ */
+ public static final int SATELLITE_SERVICE_MODEM_ERROR = 4;
+ /**
+ * System error received from the satellite service.
+ */
+ public static final int SATELLITE_SERVICE_SYSTEM_ERROR = 5;
+ /**
+ * Invalid arguments passed.
+ */
+ public static final int SATELLITE_SERVICE_INVALID_ARGUMENTS = 6;
+ /**
+ * Invalid modem state.
+ */
+ public static final int SATELLITE_SERVICE_INVALID_MODEM_STATE = 7;
+ /**
+ * Invalid SIM state.
+ */
+ public static final int SATELLITE_SERVICE_INVALID_SIM_STATE = 8;
+ /**
+ * Invalid state.
+ */
+ public static final int SATELLITE_SERVICE_INVALID_STATE = 9;
+ /**
+ * Satellite service is unavailable.
+ */
+ public static final int SATELLITE_SERVICE_NOT_AVAILABLE = 10;
+ /**
+ * Satellite service is not supported by the device or OS.
+ */
+ public static final int SATELLITE_SERVICE_NOT_SUPPORTED = 11;
+ /**
+ * Satellite service is rate limited.
+ */
+ public static final int SATELLITE_SERVICE_RATE_LIMITED = 12;
+ /**
+ * Satellite service has no memory available.
+ */
+ public static final int SATELLITE_SERVICE_NO_MEMORY = 13;
+ /**
+ * Satellite service has no resources available.
+ */
+ public static final int SATELLITE_SERVICE_NO_RESOURCES = 14;
+ /**
+ * Failed to send a request to the satellite service.
+ */
+ public static final int SATELLITE_SERVICE_REQUEST_FAILED = 15;
+ /**
+ * Failed to send a request to the satellite service for the given subscription ID.
+ */
+ public static final int SATELLITE_SERVICE_INVALID_SUBSCRIPTION_ID = 16;
+ /**
+ * Error received from satellite service.
+ */
+ public static final int SATELLITE_SERVICE_ERROR = 17;
+ /**
+ * Satellite service is disabled on the requested subscription.
+ */
+ public static final int SATELLITE_SERVICE_DISABLED = 18;
+
+ /** @hide */
+ @IntDef(prefix = {"SATELLITE_SERVICE_"}, value = {
+ SATELLITE_SERVICE_SUCCESS,
+ SATELLITE_SERVICE_SERVER_NOT_REACHABLE,
+ SATELLITE_SERVICE_SERVER_ERROR,
+ SATELLITE_SERVICE_TELEPHONY_INTERNAL_ERROR,
+ SATELLITE_SERVICE_MODEM_ERROR,
+ SATELLITE_SERVICE_SYSTEM_ERROR,
+ SATELLITE_SERVICE_INVALID_ARGUMENTS,
+ SATELLITE_SERVICE_INVALID_MODEM_STATE,
+ SATELLITE_SERVICE_INVALID_SIM_STATE,
+ SATELLITE_SERVICE_INVALID_STATE,
+ SATELLITE_SERVICE_NOT_AVAILABLE,
+ SATELLITE_SERVICE_NOT_SUPPORTED,
+ SATELLITE_SERVICE_RATE_LIMITED,
+ SATELLITE_SERVICE_NO_MEMORY,
+ SATELLITE_SERVICE_NO_RESOURCES,
+ SATELLITE_SERVICE_REQUEST_FAILED,
+ SATELLITE_SERVICE_INVALID_SUBSCRIPTION_ID,
+ SATELLITE_SERVICE_ERROR,
+ SATELLITE_SERVICE_DISABLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SatelliteServiceResult {}
+
+ /**
+ * Message transfer is waiting to acquire.
+ */
+ public static final int SATELLITE_MESSAGE_TRANSFER_STATE_WAITING_TO_ACQUIRE = 0;
+ /**
+ * Message is being sent.
+ */
+ public static final int SATELLITE_MESSAGE_TRANSFER_STATE_SENDING = 1;
+ /**
+ * Message is being received.
+ */
+ public static final int SATELLITE_MESSAGE_TRANSFER_STATE_RECEIVING = 2;
+ /**
+ * Message transfer is being retried.
+ */
+ public static final int SATELLITE_MESSAGE_TRANSFER_STATE_RETRYING = 3;
+ /**
+ * Message transfer is complete.
+ */
+ public static final int SATELLITE_MESSAGE_TRANSFER_STATE_COMPLETE = 4;
+
+ /** @hide */
+ @IntDef(prefix = {"SATELLITE_MESSAGE_TRANSFER_STATE_"}, value = {
+ SATELLITE_MESSAGE_TRANSFER_STATE_WAITING_TO_ACQUIRE,
+ SATELLITE_MESSAGE_TRANSFER_STATE_SENDING,
+ SATELLITE_MESSAGE_TRANSFER_STATE_RECEIVING,
+ SATELLITE_MESSAGE_TRANSFER_STATE_RETRYING,
+ SATELLITE_MESSAGE_TRANSFER_STATE_COMPLETE
+ })
+ public @interface SatelliteMessageTransferState {}
+
+ /**
+ * Callback for position updates from the satellite service.
+ */
+ public interface SatellitePositionUpdateCallback {
+ /**
+ * Called when the satellite position changes.
+ *
+ * @param pointingInfo The pointing info containing the satellite location.
+ */
+ void onSatellitePositionUpdate(@NonNull PointingInfo pointingInfo);
+
+ /**
+ * Called when satellite message transfer state changes.
+ *
+ * @param state The new message transfer state.
+ */
+ void onMessageTransferStateUpdate(@SatelliteMessageTransferState int state);
+ }
+
+ /**
+ * Start receiving satellite position updates.
+ * This can be called by the pointing UI when the user starts pointing to the satellite.
+ * Modem should continue to report the pointing input as the device or satellite moves.
+ * Satellite position updates are started only on {@link #SATELLITE_SERVICE_SUCCESS}.
+ * All other results indicate that this operation failed.
+ *
+ * @param executor The executor to run callbacks on.
+ * @param callback The callback to notify of changes in satellite position.
+ * @return The result of the operation.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @SatelliteServiceResult public int startSatellitePositionUpdates(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull SatellitePositionUpdateCallback callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ int id;
+ if (mSatellitePositionUpdateCallbacks.containsKey(callback)) {
+ id = mSatellitePositionUpdateCallbacks.get(callback);
+ } else {
+ id = mSatellitePositionUpdateCallbackId.getAndIncrement();
+ }
+ int result = telephony.startSatellitePositionUpdates(mSubId, id,
+ new ISatellitePositionUpdateCallback.Stub() {
+ @Override
+ public void onSatellitePositionUpdate(
+ @NonNull PointingInfo pointingInfo) {
+ logd("onSatellitePositionUpdate: pointingInfo=" + pointingInfo);
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onSatellitePositionUpdate(pointingInfo)));
+ }
+
+ @Override
+ public void onMessageTransferStateUpdate(
+ @SatelliteMessageTransferState int state) {
+ logd("onMessageTransferStateUpdate: state=" + state);
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onMessageTransferStateUpdate(state)));
+ }
+ });
+ if (result == SATELLITE_SERVICE_SUCCESS) {
+ mSatellitePositionUpdateCallbacks.put(callback, id);
+ }
+ return result;
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("startSatellitePositionUpdates RemoteException: " + ex);
+ ex.rethrowFromSystemServer();
+ }
+ return SATELLITE_SERVICE_REQUEST_FAILED;
+ }
+
+ /**
+ * Stop receiving satellite position updates.
+ * This can be called by the pointing UI when the user stops pointing to the satellite.
+ * Satellite position updates are stopped only on {@link #SATELLITE_SERVICE_SUCCESS}.
+ * All other results indicate that this operation failed.
+ *
+ * @param callback The callback that was passed in {@link
+ * #startSatellitePositionUpdates(Executor, SatellitePositionUpdateCallback)}.
+ * @return The result of the operation.
+ * @throws IllegalArgumentException if the callback is invalid.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @SatelliteServiceResult public int stopSatellitePositionUpdates(
+ @NonNull SatellitePositionUpdateCallback callback) {
+ Objects.requireNonNull(callback);
+
+ if (!mSatellitePositionUpdateCallbacks.containsKey(callback)) {
+ throw new IllegalArgumentException(
+ "startSatellitePositionUpdates was never called with the callback provided.");
+ }
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ int result = telephony.stopSatellitePositionUpdates(mSubId,
+ mSatellitePositionUpdateCallbacks.get(callback));
+ if (result == SATELLITE_SERVICE_SUCCESS) {
+ mSatellitePositionUpdateCallbacks.remove(callback);
+ // TODO: Notify SmsHandler that pointing UI stopped
+ }
+ return result;
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("stopSatellitePositionUpdates RemoteException: " + ex);
+ ex.rethrowFromSystemServer();
+ }
+ return SATELLITE_SERVICE_REQUEST_FAILED;
+ }
+
+ /**
+ * Get maximum number of characters per text message on satellite.
+ * @param executor - The executor on which the result listener will be called.
+ * @param resultListener - Listener that will be called when the operation is successful.
+ * If this method returns {@link #SATELLITE_SERVICE_SUCCESS}, listener
+ * will be called with maximum characters limit.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ *
+ * @return The result of the operation
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @SatelliteServiceResult
+ public int getMaxCharactersPerSatelliteTextMessage(@NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<Integer> resultListener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(resultListener);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() {
+ @Override
+ public void accept(int result) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(result)));
+ }
+ };
+
+ return telephony.getMaxCharactersPerSatelliteTextMessage(mSubId, internalCallback);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("getMaxCharactersPerSatelliteTextMessage() RemoteException:" + ex);
+ ex.rethrowFromSystemServer();
+ }
+ return SATELLITE_SERVICE_REQUEST_FAILED;
+ }
+
+ private static ITelephony getITelephony() {
+ ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getTelephonyServiceRegisterer()
+ .get());
+ if (binder == null) {
+ throw new RuntimeException("Could not find Telephony Service.");
+ }
+ return binder;
+ }
+
+ private static void logd(@NonNull String log) {
+ Rlog.d(TAG, log);
+ }
+
+ private static void loge(@NonNull String log) {
+ Rlog.e(TAG, log);
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
index 3dfc81e..b4d93fd 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
@@ -180,6 +180,19 @@
String getIsimImpi(int subId);
/**
+ * Fetches the ISIM PrivateUserIdentity (EF_IMPI) based on subId
+ *
+ * @param subId subscriptionId
+ * @return IMPI (IMS private user identity) of type string or null if EF_IMPI is not available.
+ * @throws IllegalArgumentException if the subscriptionId is not valid
+ * @throws IllegalStateException in case the ISIM hasn’t been loaded.
+ * @throws SecurityException if the caller does not have the required permission
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER)")
+ String getImsPrivateUserIdentity(int subId, String callingPackage, String callingFeatureId);
+
+ /**
* Returns the IMS home network domain name that was loaded from the ISIM.
* @return the IMS domain name, or null if not present or not loaded
*/
@@ -193,6 +206,14 @@
String[] getIsimImpu(int subId);
/**
+ * Fetches the ISIM public user identities (EF_IMPU) from UICC based on subId
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" +
+ "anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE})")
+ List<Uri> getImsPublicUserIdentities(int subId, String callingPackage,
+ String callingFeatureId);
+
+ /**
* Returns the IMS Service Table (IST) that was loaded from the ISIM.
* @return IMS Service Table or null if not present or not loaded
*/
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 48b1657..5486365 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -66,6 +66,7 @@
import android.telephony.ims.aidl.IImsRegistration;
import android.telephony.ims.aidl.IImsRegistrationCallback;
import android.telephony.ims.aidl.IRcsConfigCallback;
+import android.telephony.satellite.ISatellitePositionUpdateCallback;
import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.telephony.CellNetworkScanResult;
import com.android.internal.telephony.IBooleanConsumer;
@@ -2636,14 +2637,14 @@
*/
boolean isRemovableEsimDefaultEuicc(String callingPackage);
- /**
- * Get the component name of the default app to direct respond-via-message intent for the
- * user associated with this subscription, update the cache if there is no respond-via-message
- * application currently configured for this user.
- * @return component name of the app and class to direct Respond Via Message intent to, or
- * {@code null} if the functionality is not supported.
- * @hide
- */
+ /**
+ * Get the component name of the default app to direct respond-via-message intent for the
+ * user associated with this subscription, update the cache if there is no respond-via-message
+ * application currently configured for this user.
+ * @return component name of the app and class to direct Respond Via Message intent to, or
+ * {@code null} if the functionality is not supported.
+ * @hide
+ */
ComponentName getDefaultRespondViaMessageApplication(int subId, boolean updateIfNeeded);
/**
@@ -2667,12 +2668,12 @@
void setNullCipherAndIntegrityEnabled(boolean enabled);
/**
- * Get whether the radio is able to connect with null ciphering or integrity
- * algorithms. Note that this retrieves the phone-global preference and not
- * the state of the radio.
- *
- * @hide
- */
+ * Get whether the radio is able to connect with null ciphering or integrity
+ * algorithms. Note that this retrieves the phone-global preference and not
+ * the state of the radio.
+ *
+ * @hide
+ */
boolean isNullCipherAndIntegrityPreferenceEnabled();
/**
@@ -2696,5 +2697,21 @@
/**
* Get the carrier restriction status of the device.
*/
- void getCarrierRestrictionStatus(IIntegerConsumer internalCallback, String packageName);
-}
+ void getCarrierRestrictionStatus(IIntegerConsumer internalCallback, String packageName);
+
+ /**
+ * Start receiving satellite pointing updates.
+ */
+ int startSatellitePositionUpdates(int subId, int callbackId,
+ in ISatellitePositionUpdateCallback callback);
+
+ /**
+ * Stop receiving satellite pointing updates.
+ */
+ int stopSatellitePositionUpdates(int subId, int callbackId);
+
+ /**
+ * Get maximum number of characters per text message on satellite.
+ */
+ int getMaxCharactersPerSatelliteTextMessage(int subId, IIntegerConsumer internalCallback);
+}
\ No newline at end of file
diff --git a/test-mock/api/system-current.txt b/test-mock/api/system-current.txt
index f87785b..9e022f0 100644
--- a/test-mock/api/system-current.txt
+++ b/test-mock/api/system-current.txt
@@ -5,9 +5,7 @@
method public android.content.Context createCredentialProtectedStorageContext();
method public java.io.File getPreloadsFileCache();
method public boolean isCredentialProtectedStorage();
- method public void sendBroadcast(android.content.Intent, String, android.os.Bundle);
method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, String, android.os.Bundle);
- method public void sendOrderedBroadcast(android.content.Intent, String, android.os.Bundle, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle);
}
@Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
diff --git a/tests/BinaryTransparencyHostTest/Android.bp b/tests/BinaryTransparencyHostTest/Android.bp
index 142e3dd..dc6bdff 100644
--- a/tests/BinaryTransparencyHostTest/Android.bp
+++ b/tests/BinaryTransparencyHostTest/Android.bp
@@ -35,6 +35,7 @@
data: [
":BinaryTransparencyTestApp",
":EasterEgg",
+ ":com.android.apex.cts.shim.v2_rebootless_prebuilt",
],
test_suites: [
"general-tests",
diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
index 84bed92..6fe548f 100644
--- a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
+++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
@@ -16,27 +16,38 @@
package android.transparency.test;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
-import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.TimeUnit;
+
// TODO: Add @Presubmit
@RunWith(DeviceJUnit4ClassRunner.class)
public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test {
private static final String PACKAGE_NAME = "android.transparency.test.app";
- @After
- public void tearDown() throws Exception {
- uninstallPackage("com.android.egg");
+ private static final String JOB_ID = "1740526926";
+
+ /** Waiting time for the job to be scheduled */
+ private static final int JOB_CREATION_MAX_SECONDS = 30;
+
+ @Before
+ public void setUp() throws Exception {
+ cancelPendingJob();
}
@Test
@@ -59,13 +70,41 @@
@Test
public void testCollectAllUpdatedPreloadInfo() throws Exception {
- installPackage("EasterEgg.apk");
- runDeviceTest("testCollectAllUpdatedPreloadInfo");
+ try {
+ updatePreloadApp();
+ runDeviceTest("testCollectAllUpdatedPreloadInfo");
+ } finally {
+ // No need to wait until job complete, since we can't verifying very meaningfully.
+ cancelPendingJob();
+ uninstallPackage("com.android.egg");
+ }
}
@Test
- public void testMeasureMbas() throws Exception {
- // TODO(265244016): figure out a way to install an MBA
+ public void testRebootlessApexUpdateTriggersJobScheduling() throws Exception {
+ try {
+ installRebootlessApex();
+
+ // Verify
+ expectJobToBeScheduled();
+ } finally {
+ // No need to wait until job complete, since we can't verifying very meaningfully.
+ uninstallRebootlessApexThenReboot();
+ }
+ }
+
+ @Test
+ public void testPreloadUpdateTriggersJobScheduling() throws Exception {
+ try {
+ updatePreloadApp();
+
+ // Verify
+ expectJobToBeScheduled();
+ } finally {
+ // No need to wait until job complete, since we can't verifying very meaningfully.
+ cancelPendingJob();
+ uninstallPackage("com.android.egg");
+ }
}
private void runDeviceTest(String method) throws DeviceNotAvailableException {
@@ -74,4 +113,62 @@
options.setTestMethodName(method);
runDeviceTests(options);
}
+
+ private void cancelPendingJob() throws DeviceNotAvailableException {
+ CommandResult result = getDevice().executeShellV2Command(
+ "cmd jobscheduler cancel android " + JOB_ID);
+ if (result.getStatus() == CommandStatus.SUCCESS) {
+ CLog.d("Canceling, output: " + result.getStdout());
+ } else {
+ CLog.d("Something went wrong, error: " + result.getStderr());
+ }
+ }
+
+ private void expectJobToBeScheduled() throws Exception {
+ for (int i = 0; i < JOB_CREATION_MAX_SECONDS; i++) {
+ CommandResult result = getDevice().executeShellV2Command(
+ "cmd jobscheduler get-job-state android " + JOB_ID);
+ String state = result.getStdout().toString();
+ CLog.i("Job status: " + state);
+ if (state.startsWith("unknown")) {
+ // The job hasn't been scheduled yet. So try again.
+ TimeUnit.SECONDS.sleep(1);
+ } else if (result.getExitCode() != 0) {
+ fail("Failing due to unexpected job state: " + result);
+ } else {
+ // The job exists, which is all we care about here
+ return;
+ }
+ }
+ fail("Timed out waiting for the job to be scheduled");
+ }
+
+ private void installRebootlessApex() throws Exception {
+ installPackage("com.android.apex.cts.shim.v2_rebootless.apex", "--force-non-staged");
+ }
+
+ private void uninstallRebootlessApexThenReboot() throws DeviceNotAvailableException {
+ // Reboot only if the APEX is not the pre-install one.
+ CommandResult result = getDevice().executeShellV2Command(
+ "pm list packages -f --apex-only |grep com.android.apex.cts.shim");
+ assertTrue(result.getStatus() == CommandStatus.SUCCESS);
+ if (result.getStdout().contains("/data/apex/active/")) {
+ uninstallPackage("com.android.apex.cts.shim");
+ getDevice().reboot();
+
+ // Reboot enforces SELinux. Make it permissive again.
+ CommandResult runResult = getDevice().executeShellV2Command("setenforce 0");
+ assertTrue(runResult.getStatus() == CommandStatus.SUCCESS);
+ }
+ }
+
+ private void updatePreloadApp() throws DeviceNotAvailableException {
+ CommandResult result = getDevice().executeShellV2Command("pm path com.android.egg");
+ assertTrue(result.getStatus() == CommandStatus.SUCCESS);
+ assertThat(result.getStdout()).startsWith("package:/system/app/");
+ String path = result.getStdout().replaceFirst("^package:", "");
+
+ result = getDevice().executeShellV2Command("pm install " + path);
+ assertTrue(result.getStatus() == CommandStatus.SUCCESS);
+ }
}
diff --git a/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
index aedb366..176bc28e 100644
--- a/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
+++ b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
@@ -33,6 +33,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.HexFormat;
import java.util.stream.Collectors;
@@ -56,11 +57,12 @@
assertThat(args).isNotNull();
int number = Integer.valueOf(args.getString("apex-number"));
assertThat(number).isGreaterThan(0);
- var expectedApexNames = new HashSet<String>();
+ var expectedApexNames = new ArrayList<String>();
for (var i = 0; i < number; i++) {
String moduleName = args.getString("apex-" + Integer.toString(i));
expectedApexNames.add(moduleName);
}
+ assertThat(expectedApexNames).containsNoDuplicates();
// Action
var apexInfoList = mBt.collectAllApexInfo(/* includeTestOnly */ true);
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
similarity index 94%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
index fed953c..67d3195 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
@@ -31,7 +31,7 @@
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import java.util.regex.Pattern
-class ImeAppAutoFocusHelper
+class ImeShownOnAppStartHelper
@JvmOverloads
constructor(
instr: Instrumentation,
@@ -121,11 +121,14 @@
}
fun toggleFixPortraitOrientation(wmHelper: WindowManagerStateHelper) {
- val button = uiDevice.wait(Until.findObject(By.res(getPackage(),
- "toggle_fixed_portrait_btn")), FIND_TIMEOUT)
+ val button =
+ uiDevice.wait(
+ Until.findObject(By.res(getPackage(), "toggle_fixed_portrait_btn")),
+ FIND_TIMEOUT
+ )
require(button != null) {
"Button not found, this usually happens when the device " +
- "was left in an unknown state (e.g. Screen turned off)"
+ "was left in an unknown state (e.g. Screen turned off)"
}
button.click()
mInstrumentation.waitForIdleSync()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 18792a8..d9aec10 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -294,10 +294,10 @@
.StateSyncBuilder()
.add("pipWindowMinimized") {
val pipAppWindow =
- it.wmState.visibleWindows.firstOrNull { window ->
- this.windowMatchesAnyOf(window)
- }
- ?: return@add false
+ it.wmState.visibleWindows.firstOrNull { window ->
+ this.windowMatchesAnyOf(window)
+ }
+ ?: return@add false
val pipRegion = pipAppWindow.frameRegion
return@add windowRect.coversMoreThan(pipRegion)
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTestCfArm.kt
deleted file mode 100644
index 70443d8..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTestCfArm.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.ime
-
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeAutoOpenWindowToAppTestCfArm(flicker: FlickerTest) :
- CloseImeAutoOpenWindowToAppTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTestCfArm.kt
deleted file mode 100644
index 2ffc757..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTestCfArm.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.ime
-
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeAutoOpenWindowToHomeTestCfArm(flicker: FlickerTest) :
- CloseImeAutoOpenWindowToHomeTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
similarity index 97%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
index c72405c..0f59d81 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
@@ -37,7 +37,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeEditorPopupDialogTest(flicker: FlickerTest) : BaseTest(flicker) {
+class CloseImeOnDismissPopupDialogTest(flicker: FlickerTest) : BaseTest(flicker) {
private val imeTestApp = ImeEditorPopupDialogAppHelper(instrumentation)
/** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
similarity index 97%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
index 840575a..b40720b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
@@ -41,7 +41,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeWindowToHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class CloseImeOnGoHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = ImeAppHelper(instrumentation)
/** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTestCfArm.kt
similarity index 92%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTestCfArm.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTestCfArm.kt
index 34bd455..96b23bc 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTestCfArm.kt
@@ -26,4 +26,4 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowTestCfArm(flicker: FlickerTest) : OpenImeWindowTest(flicker)
+class CloseImeOnGoHomeTestCfArm(flicker: FlickerTest) : CloseImeOnGoHomeTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
similarity index 92%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
index 2081424..aa1e772 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
@@ -22,7 +22,7 @@
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
-import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.service.PlatformConsts
@@ -48,8 +48,8 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeAutoOpenWindowToHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
+open class CloseImeShownOnAppStartOnGoHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTestCfArm.kt
similarity index 90%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTestCfArm.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTestCfArm.kt
index 0e6cb53..ed5d309 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTestCfArm.kt
@@ -26,5 +26,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowFromFixedOrientationAppTestCfArm(flicker: FlickerTest) :
- OpenImeWindowFromFixedOrientationAppTest(flicker)
+open class CloseImeShownOnAppStartOnGoHomeTestCfArm(flicker: FlickerTest) :
+ CloseImeShownOnAppStartOnGoHomeTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
similarity index 91%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
index 522f9e7..dfbfde8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
@@ -22,7 +22,7 @@
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
-import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.service.PlatformConsts
@@ -48,8 +48,8 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeAutoOpenWindowToAppTest(flicker: FlickerTest) : BaseTest(flicker) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
+open class CloseImeShownOnAppStartToAppOnPressBackTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTestCfArm.kt
similarity index 88%
copy from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
copy to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTestCfArm.kt
index 87d9abd..0a89991 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTestCfArm.kt
@@ -26,4 +26,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToHomeTestCfArm(flicker: FlickerTest) : CloseImeWindowToHomeTest(flicker)
+class CloseImeShownOnAppStartToAppOnPressBackTestCfArm(flicker: FlickerTest) :
+ CloseImeShownOnAppStartToAppOnPressBackTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
similarity index 97%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
index 13feeb1..f6838f4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
@@ -43,7 +43,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeWindowToAppTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class CloseImeToAppOnPressBackTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = ImeAppHelper(instrumentation)
/** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTestCfArm.kt
similarity index 90%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTestCfArm.kt
index 87d9abd..37e8c6b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTestCfArm.kt
@@ -26,4 +26,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToHomeTestCfArm(flicker: FlickerTest) : CloseImeWindowToHomeTest(flicker)
+class CloseImeToAppOnPressBackTestCfArm(flicker: FlickerTest) :
+ CloseImeToAppOnPressBackTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
similarity index 97%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
index 7d1f6cb..a744cd7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
@@ -46,7 +46,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenImeWindowAndCloseTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class CloseImeToHomeOnFinishActivityTest(flicker: FlickerTest) : BaseTest(flicker) {
private val simpleApp = SimpleAppHelper(instrumentation)
private val testApp = ImeAppHelper(instrumentation)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTestCfArm.kt
similarity index 89%
copy from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
copy to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTestCfArm.kt
index 87d9abd..116bc1b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTestCfArm.kt
@@ -26,4 +26,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToHomeTestCfArm(flicker: FlickerTest) : CloseImeWindowToHomeTest(flicker)
+open class CloseImeToHomeOnFinishActivityTestCfArm(flicker: FlickerTest) :
+ CloseImeToHomeOnFinishActivityTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTestCfArm.kt
deleted file mode 100644
index f40f5e6..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTestCfArm.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.ime
-
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToAppTestCfArm(flicker: FlickerTest) : CloseImeWindowToAppTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTestCfArm.kt
deleted file mode 100644
index 04cae53..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTestCfArm.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.ime
-
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LaunchAppShowImeOnStartTestCfArm(flicker: FlickerTest) :
- LaunchAppShowImeOnStartTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTestCfArm.kt
deleted file mode 100644
index c40dfae..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTestCfArm.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.ime
-
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenImeWindowAndCloseTestCfArm(flicker: FlickerTest) :
- OpenImeWindowAndCloseTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
index 806ce99..09ef54f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
@@ -19,11 +19,11 @@
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.FlickerBuilder
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
-import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.traces.region.RegionSubject
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.service.PlatformConsts
@@ -34,15 +34,15 @@
import org.junit.runners.Parameterized
/**
- * Test IME window shown on the app with fixing portrait orientation.
- * To run this test: `atest FlickerTests:OpenImeWindowToFixedPortraitAppTest`
+ * Test IME window shown on the app with fixing portrait orientation. To run this test: `atest
+ * FlickerTests:OpenImeWindowToFixedPortraitAppTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class OpenImeWindowToFixedPortraitAppTest(flicker: FlickerTest) : BaseTest(flicker) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
+ private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
@@ -52,9 +52,7 @@
// Enable letterbox when the app calls setRequestedOrientation
device.executeShellCommand("cmd window set-ignore-orientation-request true")
}
- transitions {
- testApp.toggleFixPortraitOrientation(wmHelper)
- }
+ transitions { testApp.toggleFixPortraitOrientation(wmHelper) }
teardown {
testApp.exit()
device.executeShellCommand("cmd window set-ignore-orientation-request false")
@@ -64,17 +62,13 @@
@Postsubmit
@Test
fun imeLayerVisibleStart() {
- flicker.assertLayersStart {
- this.isVisible(ComponentNameMatcher.IME)
- }
+ flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.IME) }
}
@Postsubmit
@Test
fun imeLayerExistsEnd() {
- flicker.assertLayersEnd {
- this.isVisible(ComponentNameMatcher.IME)
- }
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.IME) }
}
@Postsubmit
@@ -86,7 +80,7 @@
}
flicker.assertLayersEnd {
this.visibleRegion(ComponentNameMatcher.IME)
- .coversExactly(imeLayerVisibleRegionBeforeTransition!!.region)
+ .coversExactly(imeLayerVisibleRegionBeforeTransition!!.region)
}
}
@@ -96,7 +90,7 @@
val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
flicker.assertLayersEnd {
this.visibleRegion(testApp.or(ComponentNameMatcher.LETTERBOX))
- .coversExactly(displayBounds)
+ .coversExactly(displayBounds)
}
}
@@ -111,14 +105,13 @@
@JvmStatic
fun getParams(): Collection<FlickerTest> {
return FlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(
- PlatformConsts.Rotation.ROTATION_90,
- ),
- supportedNavigationModes = listOf(
- PlatformConsts.NavBar.MODE_3BUTTON,
- PlatformConsts.NavBar.MODE_GESTURAL
- )
- )
+ supportedRotations =
+ listOf(
+ PlatformConsts.Rotation.ROTATION_90,
+ ),
+ supportedNavigationModes =
+ listOf(PlatformConsts.NavBar.MODE_3BUTTON, PlatformConsts.NavBar.MODE_GESTURAL)
+ )
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTestCfArm.kt
deleted file mode 100644
index 8e6d7dc..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTestCfArm.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.ime
-
-import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowToOverViewTestCfArm(flicker: FlickerTest) : OpenImeWindowToOverViewTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
similarity index 92%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
index d41843f..89d37db 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
@@ -23,7 +23,7 @@
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
-import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
@@ -45,8 +45,10 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenImeWindowFromFixedOrientationAppTest(flicker: FlickerTest) : BaseTest(flicker) {
- private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
+open class ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest(flicker: FlickerTest) :
+ BaseTest(flicker) {
+ private val imeTestApp =
+ ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm.kt
similarity index 86%
copy from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
copy to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm.kt
index 87d9abd..307821f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm.kt
@@ -26,4 +26,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToHomeTestCfArm(flicker: FlickerTest) : CloseImeWindowToHomeTest(flicker)
+class ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm(flicker: FlickerTest) :
+ ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
similarity index 94%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
index 38791a2..8b25d7a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
@@ -22,7 +22,7 @@
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
-import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
@@ -41,8 +41,9 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ReOpenImeWindowTest(flicker: FlickerTest) : BaseTest(flicker) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
+open class ShowImeOnAppStartWhenLaunchingAppFromOverviewTest(flicker: FlickerTest) :
+ BaseTest(flicker) {
+ private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
similarity index 94%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
index 477ddb3..1e908d6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
@@ -23,7 +23,7 @@
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
-import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
@@ -47,9 +47,11 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Presubmit
-open class SwitchImeWindowsFromGestureNavTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest(flicker: FlickerTest) :
+ BaseTest(flicker) {
private val testApp = SimpleAppHelper(instrumentation)
- private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
+ private val imeTestApp =
+ ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
@Before
open fun before() {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm.kt
similarity index 87%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTestCfArm.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm.kt
index 1d3658e..82c38a3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm.kt
@@ -28,5 +28,5 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Presubmit
-open class SwitchImeWindowsFromGestureNavTestCfArm(flicker: FlickerTest) :
- SwitchImeWindowsFromGestureNavTest(flicker)
+open class ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm(flicker: FlickerTest) :
+ ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestShellTransit.kt
similarity index 93%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestShellTransit.kt
index e297e89..6c6003f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestShellTransit.kt
@@ -38,8 +38,8 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchImeWindowsFromGestureNavTest_ShellTransit(flicker: FlickerTest) :
- SwitchImeWindowsFromGestureNavTest(flicker) {
+class ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestShellTransit(flicker: FlickerTest) :
+ ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest(flicker) {
@Before
override fun before() {
Assume.assumeTrue(isShellTransitionsEnabled)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
similarity index 94%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
index 6685cf7..0fced8c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
@@ -22,7 +22,7 @@
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
-import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.helpers.ImeStateInitializeHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
@@ -72,8 +72,8 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class LaunchAppShowImeOnStartTest(flicker: FlickerTest) : BaseTest(flicker) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
+open class ShowImeOnAppStartWhenLaunchingAppTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
private val initializeApp = ImeStateInitializeHelper(instrumentation)
/** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnStartWhenLaunchingAppCfArmTest.kt
similarity index 89%
copy from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
copy to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnStartWhenLaunchingAppCfArmTest.kt
index 87d9abd..e7ecb87 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnStartWhenLaunchingAppCfArmTest.kt
@@ -26,4 +26,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToHomeTestCfArm(flicker: FlickerTest) : CloseImeWindowToHomeTest(flicker)
+class ShowImeOnStartWhenLaunchingAppCfArmTest(flicker: FlickerTest) :
+ ShowImeOnAppStartWhenLaunchingAppTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
similarity index 96%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
index 5b830e5..36747cc7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
@@ -37,7 +37,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenImeWindowTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class ShowImeWhenFocusingOnInputFieldTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = ImeAppHelper(instrumentation)
/** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTestCfArm.kt
similarity index 89%
copy from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
copy to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTestCfArm.kt
index 87d9abd..0c53155 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTestCfArm.kt
@@ -26,4 +26,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToHomeTestCfArm(flicker: FlickerTest) : CloseImeWindowToHomeTest(flicker)
+class ShowImeWhenFocusingOnInputFieldTestCfArm(flicker: FlickerTest) :
+ ShowImeWhenFocusingOnInputFieldTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
similarity index 93%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
index cb530da..7ef09c7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
@@ -25,7 +25,7 @@
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
-import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.service.PlatformConsts
@@ -45,8 +45,8 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LaunchAppShowImeAndDialogThemeAppTest(flicker: FlickerTest) : BaseTest(flicker) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
+class ShowImeWhileDismissingThemedPopupDialogTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
similarity index 96%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
index d95af9d..fd420454 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
@@ -22,7 +22,7 @@
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
-import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
@@ -46,8 +46,9 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenImeWindowToOverViewTest(flicker: FlickerTest) : BaseTest(flicker) {
- private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
+open class ShowImeWhileEnteringOverviewTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val imeTestApp =
+ ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTestCfArm.kt
similarity index 90%
copy from tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
copy to tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTestCfArm.kt
index 87d9abd..9308fbb 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTestCfArm.kt
@@ -26,4 +26,5 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToHomeTestCfArm(flicker: FlickerTest) : CloseImeWindowToHomeTest(flicker)
+class ShowImeWhileEnteringOverviewTestCfArm(flicker: FlickerTest) :
+ ShowImeWhileEnteringOverviewTest(flicker)
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index fdba9a6..80c7a21 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -120,6 +120,15 @@
</intent-filter>
</activity>
+ <activity android:name="ColorBitmapActivity"
+ android:label="Bitmaps/BitmapColors"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="com.android.test.hwui.TEST"/>
+ </intent-filter>
+ </activity>
+
<activity android:name="PathOffsetActivity"
android:label="Path/Offset"
android:exported="true">
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java
new file mode 100644
index 0000000..017de60
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorSpace;
+import android.graphics.HardwareBufferRenderer;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RenderNode;
+import android.graphics.Shader;
+import android.hardware.HardwareBuffer;
+import android.media.Image;
+import android.media.ImageWriter;
+import android.os.Bundle;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+
+import java.time.Duration;
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class ColorBitmapActivity extends Activity implements SurfaceHolder.Callback,
+ AdapterView.OnItemSelectedListener {
+
+ private static final int WIDTH = 512;
+ private static final int HEIGHT = 512;
+
+ private ImageView mImageView;
+ private SurfaceView mSurfaceView;
+ private HardwareBuffer mGradientBuffer;
+ private ImageWriter mImageWriter;
+ private ColorSpace mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
+ private String[] mColorNames = {"sRGB", "BT2020_HLG", "BT2020_PQ"};
+ private String mCurrentColorName = "sRGB";
+
+ private FutureTask<HardwareBuffer> authorGradientBuffer(HardwareBuffer buffer) {
+ HardwareBufferRenderer renderer = new HardwareBufferRenderer(buffer);
+ RenderNode node = new RenderNode("content");
+ node.setPosition(0, 0, buffer.getWidth(), buffer.getHeight());
+
+ Canvas canvas = node.beginRecording();
+ LinearGradient gradient = new LinearGradient(
+ 0, 0, buffer.getWidth(), buffer.getHeight(), 0xFF000000,
+ 0xFFFFFFFF, Shader.TileMode.CLAMP);
+ Paint paint = new Paint();
+ paint.setShader(gradient);
+ canvas.drawRect(0f, 0f, buffer.getWidth(), buffer.getHeight(), paint);
+ node.endRecording();
+
+ renderer.setContentRoot(node);
+
+ ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
+ FutureTask<HardwareBuffer> resolvedBuffer = new FutureTask<>(() -> buffer);
+ renderer.obtainRenderRequest()
+ .setColorSpace(colorSpace)
+ .draw(Executors.newSingleThreadExecutor(), result -> {
+ result.getFence().await(Duration.ofSeconds(3));
+ resolvedBuffer.run();
+ });
+ return resolvedBuffer;
+ }
+
+ private FutureTask<HardwareBuffer> getGradientBuffer() {
+ HardwareBuffer buffer = HardwareBuffer.create(
+ WIDTH, HEIGHT, PixelFormat.RGBA_8888, 1,
+ HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+ | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT);
+ return authorGradientBuffer(buffer);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ try {
+
+ mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
+
+ ArrayAdapter<String> adapter = new ArrayAdapter<>(
+ this, android.R.layout.simple_spinner_item, mColorNames);
+
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ Spinner spinner = new Spinner(this);
+ spinner.setAdapter(adapter);
+ spinner.setOnItemSelectedListener(this);
+
+ mGradientBuffer = getGradientBuffer().get();
+
+ LinearLayout linearLayout = new LinearLayout(this);
+ linearLayout.setOrientation(LinearLayout.VERTICAL);
+
+ mImageView = new ImageView(this);
+
+ mSurfaceView = new SurfaceView(this);
+ mSurfaceView.getHolder().addCallback(this);
+
+ linearLayout.addView(spinner, new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT));
+
+ linearLayout.addView(mImageView, new LinearLayout.LayoutParams(WIDTH, HEIGHT));
+ linearLayout.addView(mSurfaceView, new LinearLayout.LayoutParams(WIDTH, HEIGHT));
+
+ setContentView(linearLayout);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private ColorSpace getFromName(String name) {
+ if (name.equals("sRGB")) {
+ return ColorSpace.get(ColorSpace.Named.SRGB);
+ } else if (name.equals("BT2020_HLG")) {
+ return ColorSpace.get(ColorSpace.Named.BT2020_HLG);
+ } else if (name.equals("BT2020_PQ")) {
+ return ColorSpace.get(ColorSpace.Named.BT2020_PQ);
+ }
+
+ throw new RuntimeException("Unrecognized Colorspace!");
+ }
+
+ private void populateBuffers() {
+ Bitmap bitmap = Bitmap.wrapHardwareBuffer(
+ mGradientBuffer, ColorSpace.get(ColorSpace.Named.SRGB));
+ Bitmap copy = bitmap.copy(Bitmap.Config.ARGB_8888, false);
+ copy.setColorSpace(mColorSpace);
+ mImageView.setImageBitmap(copy);
+
+ try (Image image = mImageWriter.dequeueInputImage()) {
+ authorGradientBuffer(image.getHardwareBuffer()).get();
+ image.setDataSpace(mColorSpace.getDataSpace());
+ mImageWriter.queueInputImage(image);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ mImageWriter = new ImageWriter.Builder(holder.getSurface())
+ .setUsage(HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+ | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT
+ | HardwareBuffer.USAGE_COMPOSER_OVERLAY)
+ .build();
+ populateBuffers();
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ mImageWriter.close();
+ mImageWriter = null;
+ }
+
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ mCurrentColorName = mColorNames[position];
+ mColorSpace = getFromName(mCurrentColorName);
+ populateBuffers();
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+
+ }
+}