Merge "Override equals & hash for VirtualCameraStreamConfig" into main
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 900c902..903248f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -159,6 +159,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -301,6 +302,8 @@
private final ConnectivityController mConnectivityController;
/** Need directly for sending uid state changes */
private final DeviceIdleJobsController mDeviceIdleJobsController;
+ /** Need directly for sending exempted bucket changes */
+ private final FlexibilityController mFlexibilityController;
/** Needed to get next estimated launch time. */
private final PrefetchController mPrefetchController;
/** Needed to get remaining quota time. */
@@ -513,6 +516,10 @@
if (name == null) {
continue;
}
+ if (DEBUG) {
+ Slog.d(TAG, "DeviceConfig " + name
+ + " changed to " + properties.getString(name, null));
+ }
switch (name) {
case Constants.KEY_ENABLE_API_QUOTAS:
case Constants.KEY_ENABLE_EXECUTION_SAFEGUARDS_UDC:
@@ -2532,17 +2539,17 @@
mControllers = new ArrayList<StateController>();
mPrefetchController = new PrefetchController(this);
mControllers.add(mPrefetchController);
- final FlexibilityController flexibilityController =
+ mFlexibilityController =
new FlexibilityController(this, mPrefetchController);
- mControllers.add(flexibilityController);
+ mControllers.add(mFlexibilityController);
mConnectivityController =
- new ConnectivityController(this, flexibilityController);
+ new ConnectivityController(this, mFlexibilityController);
mControllers.add(mConnectivityController);
mControllers.add(new TimeController(this));
- final IdleController idleController = new IdleController(this, flexibilityController);
+ final IdleController idleController = new IdleController(this, mFlexibilityController);
mControllers.add(idleController);
final BatteryController batteryController =
- new BatteryController(this, flexibilityController);
+ new BatteryController(this, mFlexibilityController);
mControllers.add(batteryController);
mStorageController = new StorageController(this);
mControllers.add(mStorageController);
@@ -3191,6 +3198,13 @@
}
@Override
+ public void onExemptedBucketChanged(@NonNull ArraySet<JobStatus> changedJobs) {
+ if (changedJobs.size() > 0) {
+ mFlexibilityController.onExemptedBucketChanged(changedJobs);
+ }
+ }
+
+ @Override
public void onRestrictionStateChanged(@NonNull JobRestriction restriction,
boolean stopOvertimeJobs) {
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
@@ -3497,7 +3511,10 @@
}
final boolean shouldForceBatchJob;
- if (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiatedJob()) {
+ if (job.overrideState > JobStatus.OVERRIDE_NONE) {
+ // The job should run for some test. Don't force batch it.
+ shouldForceBatchJob = false;
+ } else if (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiatedJob()) {
// Never batch expedited or user-initiated jobs, even for RESTRICTED apps.
shouldForceBatchJob = false;
} else if (job.getEffectiveStandbyBucket() == RESTRICTED_INDEX) {
@@ -4950,6 +4967,8 @@
Slog.d(TAG, "executeRunCommand(): " + pkgName + "/" + namespace + "/" + userId
+ " " + jobId + " s=" + satisfied + " f=" + force);
+ final CountDownLatch delayLatch = new CountDownLatch(1);
+ final JobStatus js;
try {
final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
@@ -4958,7 +4977,7 @@
}
synchronized (mLock) {
- final JobStatus js = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
+ js = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
if (js == null) {
return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
}
@@ -4969,23 +4988,71 @@
// Re-evaluate constraints after the override is set in case one of the overridden
// constraints was preventing another constraint from thinking it needed to update.
for (int c = mControllers.size() - 1; c >= 0; --c) {
- mControllers.get(c).reevaluateStateLocked(uid);
+ mControllers.get(c).evaluateStateLocked(js);
}
if (!js.isConstraintsSatisfied()) {
- js.overrideState = JobStatus.OVERRIDE_NONE;
- return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
+ if (js.hasConnectivityConstraint()
+ && !js.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)
+ && js.wouldBeReadyWithConstraint(JobStatus.CONSTRAINT_CONNECTIVITY)) {
+ // Because of how asynchronous the connectivity signals are, JobScheduler
+ // may not get the connectivity satisfaction signal immediately. In this
+ // case, wait a few seconds to see if it comes in before saying the
+ // connectivity constraint isn't satisfied.
+ mHandler.postDelayed(
+ checkConstraintRunnableForTesting(
+ mHandler, js, delayLatch, 5, 1000),
+ 1000);
+ } else {
+ // There's no asynchronous signal to wait for. We can immediately say the
+ // job's constraints aren't satisfied and return.
+ js.overrideState = JobStatus.OVERRIDE_NONE;
+ return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
+ }
+ } else {
+ delayLatch.countDown();
}
-
- queueReadyJobsForExecutionLocked();
- maybeRunPendingJobsLocked();
}
} catch (RemoteException e) {
// can't happen
+ return 0;
+ }
+
+ // Choose to block the return until we're sure about the state of the connectivity job
+ // so that tests can expect a reliable state after calling the run command.
+ try {
+ delayLatch.await(7L, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Slog.e(TAG, "Couldn't wait for asynchronous constraint change", e);
+ }
+
+ synchronized (mLock) {
+ if (!js.isConstraintsSatisfied()) {
+ js.overrideState = JobStatus.OVERRIDE_NONE;
+ return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
+ }
+
+ queueReadyJobsForExecutionLocked();
+ maybeRunPendingJobsLocked();
}
return 0;
}
+ private static Runnable checkConstraintRunnableForTesting(@NonNull final Handler handler,
+ @NonNull final JobStatus js, @NonNull final CountDownLatch latch,
+ final int remainingAttempts, final long delayMs) {
+ return () -> {
+ if (remainingAttempts <= 0 || js.isConstraintsSatisfied()) {
+ latch.countDown();
+ return;
+ }
+ handler.postDelayed(
+ checkConstraintRunnableForTesting(
+ handler, js, latch, remainingAttempts - 1, delayMs),
+ delayMs);
+ };
+ }
+
// Shell command infrastructure: immediately timeout currently executing jobs
int executeStopCommand(PrintWriter pw, String pkgName, int userId,
@Nullable String namespace, boolean hasJobId, int jobId,
diff --git a/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java b/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java
index 50064bd..411a24d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java
@@ -62,6 +62,12 @@
/**
* Called when these jobs are added or removed from the
+ * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_EXEMPTED} bucket.
+ */
+ void onExemptedBucketChanged(@NonNull ArraySet<JobStatus> jobs);
+
+ /**
+ * Called when these jobs are added or removed from the
* {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket.
*/
void onRestrictedBucketChanged(@NonNull List<JobStatus> jobs);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 0e67b9a..e4cb569 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -20,6 +20,7 @@
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
@@ -180,8 +181,12 @@
}
};
- private static final int MSG_UPDATE_JOBS = 0;
- private static final int MSG_UPDATE_JOB = 1;
+ private static final int MSG_CHECK_ALL_JOBS = 0;
+ /** Check the jobs in {@link #mJobsToCheck} */
+ private static final int MSG_CHECK_JOBS = 1;
+
+ @GuardedBy("mLock")
+ private final ArraySet<JobStatus> mJobsToCheck = new ArraySet<>();
public FlexibilityController(
JobSchedulerService service, PrefetchController prefetchController) {
@@ -266,7 +271,14 @@
@GuardedBy("mLock")
boolean isFlexibilitySatisfiedLocked(JobStatus js) {
return !mFlexibilityEnabled
+ // Exclude all jobs of the TOP app
|| mService.getUidBias(js.getSourceUid()) == JobInfo.BIAS_TOP_APP
+ // Only exclude DEFAULT+ priority jobs for BFGS+ apps
+ || (mService.getUidBias(js.getSourceUid()) >= JobInfo.BIAS_BOUND_FOREGROUND_SERVICE
+ && js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT)
+ // Only exclude DEFAULT+ priority jobs for EXEMPTED apps
+ || (js.getStandbyBucket() == EXEMPTED_INDEX
+ && js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT)
|| hasEnoughSatisfiedConstraintsLocked(js)
|| mService.isCurrentlyRunningLocked(js);
}
@@ -371,11 +383,19 @@
// Push the job update to the handler to avoid blocking other controllers and
// potentially batch back-to-back controller state updates together.
- mHandler.obtainMessage(MSG_UPDATE_JOBS).sendToTarget();
+ mHandler.obtainMessage(MSG_CHECK_ALL_JOBS).sendToTarget();
}
}
}
+ /** Called with a set of apps who have been added to or removed from the exempted bucket. */
+ public void onExemptedBucketChanged(@NonNull ArraySet<JobStatus> changedJobs) {
+ synchronized (mLock) {
+ mJobsToCheck.addAll(changedJobs);
+ mHandler.sendEmptyMessage(MSG_CHECK_JOBS);
+ }
+ }
+
/** Checks if the given constraint is satisfied in the flexibility controller. */
@VisibleForTesting
boolean isConstraintSatisfied(int constraint) {
@@ -485,7 +505,9 @@
@Override
@GuardedBy("mLock")
public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
- if (prevBias != JobInfo.BIAS_TOP_APP && newBias != JobInfo.BIAS_TOP_APP) {
+ if (prevBias < JobInfo.BIAS_BOUND_FOREGROUND_SERVICE
+ && newBias < JobInfo.BIAS_BOUND_FOREGROUND_SERVICE) {
+ // All changes are below BFGS. There's no significant change to care about.
return;
}
final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -710,7 +732,8 @@
}
mFlexibilityTracker.setNumDroppedFlexibleConstraints(js,
js.getNumAppliedFlexibleConstraints());
- mHandler.obtainMessage(MSG_UPDATE_JOB, js).sendToTarget();
+ mJobsToCheck.add(js);
+ mHandler.sendEmptyMessage(MSG_CHECK_JOBS);
return;
}
if (nextTimeElapsed == NO_LIFECYCLE_END) {
@@ -761,10 +784,11 @@
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_UPDATE_JOBS:
- removeMessages(MSG_UPDATE_JOBS);
+ case MSG_CHECK_ALL_JOBS:
+ removeMessages(MSG_CHECK_ALL_JOBS);
synchronized (mLock) {
+ mJobsToCheck.clear();
final long nowElapsed = sElapsedRealtimeClock.millis();
final ArraySet<JobStatus> changedJobs = new ArraySet<>();
@@ -790,19 +814,25 @@
}
break;
- case MSG_UPDATE_JOB:
+ case MSG_CHECK_JOBS:
synchronized (mLock) {
- final JobStatus js = (JobStatus) msg.obj;
- if (DEBUG) {
- Slog.d("blah", "Checking on " + js.toShortString());
- }
final long nowElapsed = sElapsedRealtimeClock.millis();
- if (js.setFlexibilityConstraintSatisfied(
- nowElapsed, isFlexibilitySatisfiedLocked(js))) {
- // TODO(141645789): add method that will take a single job
- ArraySet<JobStatus> changedJob = new ArraySet<>();
- changedJob.add(js);
- mStateChangedListener.onControllerStateChanged(changedJob);
+ ArraySet<JobStatus> changedJobs = new ArraySet<>();
+
+ for (int i = mJobsToCheck.size() - 1; i >= 0; --i) {
+ final JobStatus js = mJobsToCheck.valueAt(i);
+ if (DEBUG) {
+ Slog.d(TAG, "Checking on " + js.toShortString());
+ }
+ if (js.setFlexibilityConstraintSatisfied(
+ nowElapsed, isFlexibilitySatisfiedLocked(js))) {
+ changedJobs.add(js);
+ }
+ }
+
+ mJobsToCheck.clear();
+ if (changedJobs.size() > 0) {
+ mStateChangedListener.onControllerStateChanged(changedJobs);
}
}
break;
@@ -985,7 +1015,10 @@
pw.println(":");
pw.increaseIndent();
- pw.print(KEY_APPLIED_CONSTRAINTS, APPLIED_CONSTRAINTS).println();
+ pw.print(KEY_APPLIED_CONSTRAINTS, APPLIED_CONSTRAINTS);
+ pw.print("(");
+ JobStatus.dumpConstraints(pw, APPLIED_CONSTRAINTS);
+ pw.println(")");
pw.print(KEY_DEADLINE_PROXIMITY_LIMIT, DEADLINE_PROXIMITY_LIMIT_MS).println();
pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE, FALLBACK_FLEXIBILITY_DEADLINE_MS).println();
pw.print(KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS,
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 0d5d11e..a095a16 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
@@ -106,11 +106,8 @@
public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
public static final long NO_EARLIEST_RUNTIME = 0L;
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; // 1 << 2
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public static final int CONSTRAINT_BATTERY_NOT_LOW =
JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@@ -2194,7 +2191,7 @@
* @return Whether or not this job would be ready to run if it had the specified constraint
* granted, based on its requirements.
*/
- boolean wouldBeReadyWithConstraint(int constraint) {
+ public boolean wouldBeReadyWithConstraint(int constraint) {
return readinessStatusWithConstraint(constraint, true);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 8ddbf69..04da781 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -2511,6 +2511,7 @@
+ " to bucketIndex " + bucketIndex);
}
List<JobStatus> restrictedChanges = new ArrayList<>();
+ ArraySet<JobStatus> exemptedChanges = new ArraySet<>();
synchronized (mLock) {
ShrinkableDebits debits = mEJStats.get(userId, packageName);
if (debits != null) {
@@ -2530,6 +2531,10 @@
&& bucketIndex != js.getStandbyBucket()) {
restrictedChanges.add(js);
}
+ if ((bucketIndex == EXEMPTED_INDEX || js.getStandbyBucket() == EXEMPTED_INDEX)
+ && bucketIndex != js.getStandbyBucket()) {
+ exemptedChanges.add(js);
+ }
js.setStandbyBucket(bucketIndex);
}
Timer timer = mPkgTimers.get(userId, packageName);
@@ -2544,6 +2549,9 @@
maybeUpdateConstraintForPkgLocked(
sElapsedRealtimeClock.millis(), userId, packageName));
}
+ if (exemptedChanges.size() > 0) {
+ mStateChangedListener.onExemptedBucketChanged(exemptedChanges);
+ }
if (restrictedChanges.size() > 0) {
mStateChangedListener.onRestrictedBucketChanged(restrictedChanges);
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 69a6e6d..c6f920f 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -518,6 +518,8 @@
<permission name="android.permission.ACCESS_BROADCAST_RADIO"/>
<!-- Permission required for CTS test - CtsAmbientContextServiceTestCases -->
<permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/>
+ <!-- Permission required for CTS test - CtsWearableSensingServiceTestCases -->
+ <permission name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE"/>
<!-- Permission required for CTS test - CtsTelephonyProviderTestCases -->
<permission name="android.permission.WRITE_APN_SETTINGS"/>
<!-- Permission required for GTS test - GtsStatsdHostTestCases -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index 5b0239f..02af2d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -563,7 +563,7 @@
? p.x - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR
: p.x + mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
animationForChild(child)
- .translationX(fromX, p.y)
+ .translationX(fromX, p.x)
.start();
} else {
float fromY = p.y - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
@@ -634,4 +634,9 @@
.start();
}
}
+
+ /** Returns true if we're in the middle of a collapse or expand animation. */
+ boolean isAnimating() {
+ return mAnimatingCollapse || mAnimatingExpand;
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
index c1ff260..60f1d271 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -16,52 +16,51 @@
package com.android.wm.shell.bubbles.animation;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.annotation.SuppressLint;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.testing.AndroidTestingRunner;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleStackView;
+import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestCase {
- private int mDisplayWidth = 500;
- private int mDisplayHeight = 1000;
-
- private Runnable mOnBubbleAnimatedOutAction = mock(Runnable.class);
+ private final Semaphore mBubbleRemovedSemaphore = new Semaphore(0);
+ private final Runnable mOnBubbleAnimatedOutAction = mBubbleRemovedSemaphore::release;
ExpandedAnimationController mExpandedController;
private int mStackOffset;
private PointF mExpansionPoint;
private BubblePositioner mPositioner;
- private BubbleStackView.StackViewState mStackViewState = new BubbleStackView.StackViewState();
+ private final BubbleStackView.StackViewState mStackViewState =
+ new BubbleStackView.StackViewState();
- @SuppressLint("VisibleForTests")
@Before
public void setUp() throws Exception {
super.setUp();
@@ -70,15 +69,13 @@
getContext().getSystemService(WindowManager.class));
mPositioner.updateInternal(Configuration.ORIENTATION_PORTRAIT,
Insets.of(0, 0, 0, 0),
- new Rect(0, 0, mDisplayWidth, mDisplayHeight));
+ new Rect(0, 0, 500, 1000));
BubbleStackView stackView = mock(BubbleStackView.class);
- when(stackView.getState()).thenReturn(getStackViewState());
mExpandedController = new ExpandedAnimationController(mPositioner,
mOnBubbleAnimatedOutAction,
stackView);
- spyOn(mExpandedController);
addOneMoreThanBubbleLimitBubbles();
mLayout.setActiveController(mExpandedController);
@@ -86,9 +83,18 @@
Resources res = mLayout.getResources();
mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
mExpansionPoint = new PointF(100, 100);
+
+ getStackViewState();
+ when(stackView.getState()).thenAnswer(i -> getStackViewState());
+ waitForMainThread();
}
- public BubbleStackView.StackViewState getStackViewState() {
+ @After
+ public void tearDown() {
+ waitForMainThread();
+ }
+
+ private BubbleStackView.StackViewState getStackViewState() {
mStackViewState.numberOfBubbles = mLayout.getChildCount();
mStackViewState.selectedIndex = 0;
mStackViewState.onLeft = mPositioner.isStackOnLeft(mExpansionPoint);
@@ -96,68 +102,71 @@
}
@Test
- @Ignore
- public void testExpansionAndCollapse() throws InterruptedException {
- Runnable afterExpand = mock(Runnable.class);
- mExpandedController.expandFromStack(afterExpand);
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
-
+ public void testExpansionAndCollapse() throws Exception {
+ expand();
testBubblesInCorrectExpandedPositions();
- verify(afterExpand).run();
+ waitForMainThread();
- Runnable afterCollapse = mock(Runnable.class);
+ final Semaphore semaphore = new Semaphore(0);
+ Runnable afterCollapse = semaphore::release;
mExpandedController.collapseBackToStack(mExpansionPoint, false, afterCollapse);
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
-
- testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y, -1);
- verify(afterExpand).run();
+ assertThat(semaphore.tryAcquire(1, 2, TimeUnit.SECONDS)).isTrue();
+ waitForAnimation();
+ testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y);
}
@Test
- @Ignore
- public void testOnChildAdded() throws InterruptedException {
+ public void testOnChildAdded() throws Exception {
expand();
+ waitForMainThread();
// Add another new view and wait for its animation.
final View newView = new FrameLayout(getContext());
mLayout.addView(newView, 0);
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+ waitForAnimation();
testBubblesInCorrectExpandedPositions();
}
@Test
- @Ignore
- public void testOnChildRemoved() throws InterruptedException {
+ public void testOnChildRemoved() throws Exception {
expand();
+ waitForMainThread();
- // Remove some views and see if the remaining child views still pass the expansion test.
+ // Remove some views and verify the remaining child views still pass the expansion test.
mLayout.removeView(mViews.get(0));
mLayout.removeView(mViews.get(3));
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+ // Removing a view will invoke onBubbleAnimatedOutAction. Block until it gets called twice.
+ assertThat(mBubbleRemovedSemaphore.tryAcquire(2, 2, TimeUnit.SECONDS)).isTrue();
+
+ waitForAnimation();
testBubblesInCorrectExpandedPositions();
}
@Test
- public void testDragBubbleOutDoesntNPE() throws InterruptedException {
+ public void testDragBubbleOutDoesntNPE() {
mExpandedController.onGestureFinished();
mExpandedController.dragBubbleOut(mViews.get(0), 1, 1);
}
/** Expand the stack and wait for animations to finish. */
private void expand() throws InterruptedException {
- mExpandedController.expandFromStack(mock(Runnable.class));
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+ final Semaphore semaphore = new Semaphore(0);
+ Runnable afterExpand = semaphore::release;
+
+ mExpandedController.expandFromStack(afterExpand);
+ assertThat(semaphore.tryAcquire(1, TimeUnit.SECONDS)).isTrue();
}
/** Check that children are in the correct positions for being stacked. */
- private void testStackedAtPosition(float x, float y, int offsetMultiplier) {
+ private void testStackedAtPosition(float x, float y) {
// Make sure the rest of the stack moved again, including the first bubble not moving, and
// is stacked to the right now that we're on the right side of the screen.
for (int i = 0; i < mLayout.getChildCount(); i++) {
- assertEquals(x + i * offsetMultiplier * mStackOffset,
- mLayout.getChildAt(i).getTranslationX(), 2f);
- assertEquals(y, mLayout.getChildAt(i).getTranslationY(), 2f);
+ assertEquals(x, mLayout.getChildAt(i).getTranslationX(), 2f);
+ assertEquals(y + Math.min(i, 1) * mStackOffset, mLayout.getChildAt(i).getTranslationY(),
+ 2f);
assertEquals(1f, mLayout.getChildAt(i).getAlpha(), .01f);
}
}
@@ -175,4 +184,22 @@
mLayout.getChildAt(i).getTranslationY(), 2f);
}
}
+
+ private void waitForAnimation() throws Exception {
+ final Semaphore semaphore = new Semaphore(0);
+ boolean[] animating = new boolean[]{ true };
+ for (int i = 0; i < 4; i++) {
+ if (animating[0]) {
+ mMainThreadHandler.post(() -> {
+ if (!mExpandedController.isAnimating()) {
+ animating[0] = false;
+ semaphore.release();
+ }
+ });
+ Thread.sleep(500);
+ }
+ }
+ assertThat(semaphore.tryAcquire(1, 2, TimeUnit.SECONDS)).isTrue();
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java
index 48ae296..2ed5add 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java
@@ -164,11 +164,17 @@
@Override
public void cancelAllAnimations() {
+ if (mLayout.getChildCount() == 0) {
+ return;
+ }
mMainThreadHandler.post(super::cancelAllAnimations);
}
@Override
public void cancelAnimationsOnView(View view) {
+ if (mLayout.getChildCount() == 0) {
+ return;
+ }
mMainThreadHandler.post(() -> super.cancelAnimationsOnView(view));
}
@@ -221,6 +227,9 @@
@Override
protected void startPathAnimation() {
+ if (mLayout.getChildCount() == 0) {
+ return;
+ }
mMainThreadHandler.post(super::startPathAnimation);
}
}
@@ -322,4 +331,9 @@
e.printStackTrace();
}
}
+
+ /** Waits for the main thread to finish processing all pending runnables. */
+ public void waitForMainThread() {
+ runOnMainThreadAndBlock(() -> {});
+ }
}
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 5a72b0b..1a3d7b7 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -23,6 +23,8 @@
import android.annotation.TestApi;
import android.util.SparseIntArray;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
@@ -402,7 +404,9 @@
private final AudioDevicePort mPort;
- AudioDeviceInfo(AudioDevicePort port) {
+ /** @hide */
+ @VisibleForTesting
+ public AudioDeviceInfo(AudioDevicePort port) {
mPort = port;
}
diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java
index 73bc6f9..2de8eef 100644
--- a/media/java/android/media/AudioDevicePort.java
+++ b/media/java/android/media/AudioDevicePort.java
@@ -20,6 +20,8 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
+import com.android.aconfig.annotations.VisibleForTesting;
+
import java.util.Arrays;
import java.util.List;
@@ -38,6 +40,26 @@
public class AudioDevicePort extends AudioPort {
+ /** @hide */
+ // TODO: b/316864909 - Remove this method once there's a way to fake audio device ports further
+ // down the stack.
+ @VisibleForTesting
+ public static AudioDevicePort createForTesting(
+ int type, @NonNull String name, @NonNull String address) {
+ return new AudioDevicePort(
+ new AudioHandle(/* id= */ 0),
+ name,
+ /* samplingRates= */ null,
+ /* channelMasks= */ null,
+ /* channelIndexMasks= */ null,
+ /* formats= */ null,
+ /* gains= */ null,
+ type,
+ address,
+ /* encapsulationModes= */ null,
+ /* encapsulationMetadataTypes= */ null);
+ }
+
private final int mType;
private final String mAddress;
private final int[] mEncapsulationModes;
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 6e65c16..477c42e 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -688,6 +688,9 @@
<!-- Permission required for CTS test - CtsAmbientContextDetectionServiceDeviceTest -->
<uses-permission android:name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT" />
+ <!-- Permission required for CTS test - CtsWearableSensingServiceTestCases -->
+ <uses-permission android:name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE" />
+
<!-- Permission required for CTS test - CallAudioInterceptionTest -->
<uses-permission android:name="android.permission.CALL_AUDIO_INTERCEPTION" />
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index ab4fe76..eaa6d07 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -241,10 +241,16 @@
}
flag {
+ name: "switch_user_on_bg"
+ namespace: "systemui"
+ description: "Does user switching on a background thread"
+ bug: "284095720"
+}
+
+flag {
name: "status_bar_static_inout_indicators"
namespace: "systemui"
description: "(Upstream request) Always show the network activity inout indicators and "
"prefer using alpha to distinguish network activity."
bug: "310715220"
}
-
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index 0ab596c..1f8854f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -616,4 +616,28 @@
.onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN))
job.cancel()
}
+
+ @Test
+ fun bouncerToAod_dozeAmountChanged() =
+ testScope.runTest {
+ // GIVEN view is attached
+ mController.onViewAttached()
+ Mockito.reset(mView)
+
+ val job = mController.listenForPrimaryBouncerToAodTransitions(this)
+ // WHEN alternate bouncer to aod transition in progress
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.AOD,
+ value = .3f,
+ transitionState = TransitionState.RUNNING
+ )
+ )
+ runCurrent()
+
+ // THEN doze amount is updated to
+ verify(mView).onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATE_APPEAR_ON_SCREEN_OFF))
+ job.cancel()
+ }
}
diff --git a/packages/SystemUI/res/drawable/ic_memory.xml b/packages/SystemUI/res/drawable/ic_memory.xml
deleted file mode 100644
index ada36c5..0000000
--- a/packages/SystemUI/res/drawable/ic_memory.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT 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.0"
- android:viewportHeight="24.0">
- <path
- android:pathData="M16.0,5.0l-8.0,0.0l0.0,14.0l8.0,0.0z"
- android:fillAlpha="0.5"
- android:fillColor="#000000"/>
- <path
- android:pathData="M6,9 L6,7 L4,7 L4,5 L6,5 C6,3.9 6.9,3 8,3 L16,3 C17.1,3 18,3.9 18,5 L20,5 L20,7 L18,7 L18,9 L20,9 L20,11 L18,11 L18,13 L20,13 L20,15 L18,15 L18,17 L20,17 L20,19 L18,19 C18,20.1 17.1,21 16,21 L8,21 C6.9,21 6,20.1 6,19 L4,19 L4,17 L6,17 L6,15 L4,15 L4,13 L6,13 L6,11 L4,11 L4,9 L6,9 Z M16,19 L16,5 L8,5 L8,19 L16,19 Z"
- android:fillColor="#000000"/>
-</vector>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 90cc1fb..e01a2aa 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -480,9 +480,6 @@
This name is in the ComponentName flattened format (package/class) -->
<string name="config_remoteCopyPackage" translatable="false"></string>
- <!-- On debuggable builds, alert the user if SystemUI PSS goes over this number (in kb) -->
- <integer name="watch_heap_limit">256000</integer>
-
<!-- SystemUI Plugins that can be loaded on user builds. -->
<string-array name="config_pluginAllowlist" translatable="false">
<item>com.android.systemui</item>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e10925d..f4b25a7 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2397,10 +2397,6 @@
<!-- URl of the webpage that explains battery saver. -->
<string name="help_uri_battery_saver_learn_more_link_target" translatable="false"></string>
- <!-- Name for a quick settings tile, used only by platform developers, to extract the SystemUI process memory and send it to another
- app for debugging. Will not be seen by users. [CHAR LIMIT=20] -->
- <string name="heap_dump_tile_name">Dump SysUI Heap</string>
-
<!-- Title for the privacy indicators dialog, only appears as part of a11y descriptions [CHAR LIMIT=NONE] -->
<string name="ongoing_privacy_dialog_a11y_title">In use</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index cdd7b80..74b975c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -39,7 +39,6 @@
import androidx.annotation.VisibleForTesting;
import com.android.systemui.Dumpable;
-import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
@@ -48,9 +47,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
-import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder;
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager;
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.log.dagger.KeyguardClockLog;
@@ -62,17 +59,11 @@
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker;
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerAlwaysOnDisplayViewBinder;
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
-import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.ui.SystemBarUtilsState;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.settings.SecureSettings;
@@ -102,14 +93,7 @@
private final DumpManager mDumpManager;
private final ClockEventController mClockEventController;
private final LogBuffer mLogBuffer;
- private final NotificationIconContainerAlwaysOnDisplayViewModel mAodIconsViewModel;
- private final KeyguardRootViewModel mKeyguardRootViewModel;
- private final ConfigurationState mConfigurationState;
- private final SystemBarUtilsState mSystemBarUtilsState;
- private final DozeParameters mDozeParameters;
- private final ScreenOffAnimationController mScreenOffAnimationController;
- private final AlwaysOnDisplayNotificationIconViewStore mAodIconViewStore;
- private final StatusBarIconViewBindingFailureTracker mIconViewBindingFailureTracker;
+ private final NotificationIconContainerAlwaysOnDisplayViewBinder mNicViewBinder;
private FrameLayout mSmallClockFrame; // top aligned clock
private FrameLayout mLargeClockFrame; // centered clock
@@ -183,9 +167,7 @@
KeyguardSliceViewController keyguardSliceViewController,
NotificationIconAreaController notificationIconAreaController,
LockscreenSmartspaceController smartspaceController,
- SystemBarUtilsState systemBarUtilsState,
- ScreenOffAnimationController screenOffAnimationController,
- StatusBarIconViewBindingFailureTracker iconViewBindingFailureTracker,
+ NotificationIconContainerAlwaysOnDisplayViewBinder nicViewBinder,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
SecureSettings secureSettings,
@Main DelayableExecutor uiExecutor,
@@ -193,11 +175,6 @@
DumpManager dumpManager,
ClockEventController clockEventController,
@KeyguardClockLog LogBuffer logBuffer,
- NotificationIconContainerAlwaysOnDisplayViewModel aodIconsViewModel,
- KeyguardRootViewModel keyguardRootViewModel,
- ConfigurationState configurationState,
- DozeParameters dozeParameters,
- AlwaysOnDisplayNotificationIconViewStore aodIconViewStore,
KeyguardInteractor keyguardInteractor,
KeyguardClockInteractor keyguardClockInteractor,
FeatureFlagsClassic featureFlags,
@@ -208,9 +185,7 @@
mKeyguardSliceViewController = keyguardSliceViewController;
mNotificationIconAreaController = notificationIconAreaController;
mSmartspaceController = smartspaceController;
- mSystemBarUtilsState = systemBarUtilsState;
- mScreenOffAnimationController = screenOffAnimationController;
- mIconViewBindingFailureTracker = iconViewBindingFailureTracker;
+ mNicViewBinder = nicViewBinder;
mSecureSettings = secureSettings;
mUiExecutor = uiExecutor;
mBgExecutor = bgExecutor;
@@ -218,11 +193,6 @@
mDumpManager = dumpManager;
mClockEventController = clockEventController;
mLogBuffer = logBuffer;
- mAodIconsViewModel = aodIconsViewModel;
- mKeyguardRootViewModel = keyguardRootViewModel;
- mConfigurationState = configurationState;
- mDozeParameters = dozeParameters;
- mAodIconViewStore = aodIconViewStore;
mView.setLogBuffer(mLogBuffer);
mFeatureFlags = featureFlags;
mKeyguardInteractor = keyguardInteractor;
@@ -619,28 +589,7 @@
mAodIconsBindHandle.dispose();
}
if (nic != null) {
- final DisposableHandle viewHandle =
- NotificationIconContainerViewBinder.bindWhileAttached(
- nic,
- mAodIconsViewModel,
- mConfigurationState,
- mSystemBarUtilsState,
- mIconViewBindingFailureTracker,
- mAodIconViewStore);
- final DisposableHandle visHandle = KeyguardRootViewBinder.bindAodIconVisibility(
- nic,
- mKeyguardRootViewModel.isNotifIconContainerVisible(),
- mConfigurationState,
- mFeatureFlags,
- mScreenOffAnimationController);
- if (visHandle == null) {
- mAodIconsBindHandle = viewHandle;
- } else {
- mAodIconsBindHandle = () -> {
- viewHandle.dispose();
- visHandle.dispose();
- };
- }
+ mAodIconsBindHandle = mNicViewBinder.bindWhileAttached(nic);
mAodIconContainer = nic;
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 63fe26a..109741c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -199,11 +199,27 @@
listenForAodToOccludedTransitions(this)
listenForAlternateBouncerToAodTransitions(this)
listenForDreamingToAodTransitions(this)
+ listenForPrimaryBouncerToAodTransitions(this)
}
}
}
@VisibleForTesting
+ suspend fun listenForPrimaryBouncerToAodTransitions(scope: CoroutineScope): Job {
+ return scope.launch {
+ transitionInteractor
+ .transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.AOD)
+ .collect { transitionStep ->
+ view.onDozeAmountChanged(
+ transitionStep.value,
+ transitionStep.value,
+ ANIMATE_APPEAR_ON_SCREEN_OFF,
+ )
+ }
+ }
+ }
+
+ @VisibleForTesting
suspend fun listenForDreamingToAodTransitions(scope: CoroutineScope): Job {
return scope.launch {
transitionInteractor.transition(KeyguardState.DREAMING, KeyguardState.AOD).collect {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index b43f54d..5b9509d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -483,9 +483,6 @@
// TODO(b/264916608): Tracking Bug
@JvmField val SCREENSHOT_METADATA = unreleasedFlag("screenshot_metadata")
- // TODO(b/266955521): Tracking bug
- @JvmField val SCREENSHOT_DETECTION = releasedFlag("screenshot_detection")
-
// TODO(b/251205791): Tracking Bug
@JvmField val SCREENSHOT_APP_CLIPS = releasedFlag("screenshot_app_clips")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index de15fd6..c98f637 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -22,6 +22,7 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.res.R
import javax.inject.Inject
@@ -48,7 +49,9 @@
) {
init {
- alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG)
+ if (!DeviceEntryUdfpsRefactor.isEnabled) {
+ alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG)
+ }
}
private val showIndicatorForPrimaryBouncer: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 08e2a8f..362e7e6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -29,7 +29,6 @@
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
import com.android.internal.jank.InteractionJankMonitor
@@ -67,6 +66,7 @@
import javax.inject.Provider
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -205,7 +205,6 @@
childViews[aodNotificationIconContainerId]
?.setAodNotifIconContainerIsVisible(
isVisible,
- featureFlags,
iconsAppearTranslationPx.value,
screenOffAnimationController,
)
@@ -359,37 +358,29 @@
}
}
- @JvmStatic
- fun bindAodIconVisibility(
+ suspend fun bindAodNotifIconVisibility(
view: View,
isVisible: Flow<AnimatedValue<Boolean>>,
configuration: ConfigurationState,
- featureFlags: FeatureFlagsClassic,
screenOffAnimationController: ScreenOffAnimationController,
- ): DisposableHandle? {
+ ) {
KeyguardShadeMigrationNssl.assertInLegacyMode()
- if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return null
- return view.repeatWhenAttached {
- lifecycleScope.launch {
- val iconAppearTranslationPx =
- configuration
- .getDimensionPixelSize(R.dimen.shelf_appear_translation)
- .stateIn(this)
- isVisible.collect { isVisible ->
- view.setAodNotifIconContainerIsVisible(
- isVisible,
- featureFlags,
- iconAppearTranslationPx.value,
- screenOffAnimationController,
- )
- }
+ if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return
+ coroutineScope {
+ val iconAppearTranslationPx =
+ configuration.getDimensionPixelSize(R.dimen.shelf_appear_translation).stateIn(this)
+ isVisible.collect { isVisible ->
+ view.setAodNotifIconContainerIsVisible(
+ isVisible = isVisible,
+ iconsAppearTranslationPx = iconAppearTranslationPx.value,
+ screenOffAnimationController = screenOffAnimationController,
+ )
}
}
}
private fun View.setAodNotifIconContainerIsVisible(
isVisible: AnimatedValue<Boolean>,
- featureFlags: FeatureFlagsClassic,
iconsAppearTranslationPx: Int,
screenOffAnimationController: ScreenOffAnimationController,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 1ab64b7..ba3357c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -17,12 +17,10 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
-import android.os.Build;
import android.provider.Settings;
-import com.android.systemui.res.R;
import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.util.leak.GarbageMonitor;
+import com.android.systemui.res.R;
import java.util.ArrayList;
import java.util.Arrays;
@@ -44,10 +42,6 @@
final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
tiles.addAll(Arrays.asList(defaultTileList.split(",")));
- if (Build.IS_DEBUGGABLE
- && GarbageMonitor.ADD_MEMORY_TILE_TO_DEFAULT_ON_DEBUGGABLE_BUILDS) {
- tiles.add(GarbageMonitor.MemoryTile.TILE_SPEC);
- }
return tiles;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 2af7ae0..47b0624 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -23,7 +23,6 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
@@ -33,7 +32,6 @@
import androidx.annotation.Nullable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.qs.QSTile;
@@ -42,8 +40,8 @@
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.util.leak.GarbageMonitor;
import java.util.ArrayList;
import java.util.Arrays;
@@ -114,9 +112,6 @@
possibleTiles.add(spec);
}
}
- if (Build.IS_DEBUGGABLE && !current.contains(GarbageMonitor.MemoryTile.TILE_SPEC)) {
- possibleTiles.add(GarbageMonitor.MemoryTile.TILE_SPEC);
- }
final ArrayList<QSTile> tilesToAdd = new ArrayList<>();
possibleTiles.remove("cell");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 17e6375..bdcbac0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -14,7 +14,6 @@
package com.android.systemui.qs.tileimpl;
-import android.os.Build;
import android.util.Log;
import androidx.annotation.Nullable;
@@ -25,15 +24,14 @@
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.external.CustomTile;
-import com.android.systemui.util.leak.GarbageMonitor;
-
-import dagger.Lazy;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Provider;
+import dagger.Lazy;
+
/**
* A factory that creates Quick Settings tiles based on a tileSpec
*
@@ -79,9 +77,7 @@
@Nullable
protected QSTileImpl createTileInternal(String tileSpec) {
// Stock tiles.
- if (mTileMap.containsKey(tileSpec)
- // We should not return a Garbage Monitory Tile if the build is not Debuggable
- && (!tileSpec.equals(GarbageMonitor.MemoryTile.TILE_SPEC) || Build.IS_DEBUGGABLE)) {
+ if (mTileMap.containsKey(tileSpec)) {
return mTileMap.get(tileSpec).get();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
index 5cbea90..7130fa1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
@@ -11,8 +11,6 @@
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.constraintlayout.widget.Guideline
import com.android.systemui.res.R
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import javax.inject.Inject
/**
@@ -23,7 +21,6 @@
constructor(
private val workProfileMessageController: WorkProfileMessageController,
private val screenshotDetectionController: ScreenshotDetectionController,
- private val featureFlags: FeatureFlags,
) {
private lateinit var container: ViewGroup
private lateinit var guideline: Guideline
@@ -63,10 +60,8 @@
fun onScreenshotTaken(screenshot: ScreenshotData) {
val workProfileData = workProfileMessageController.onScreenshotTaken(screenshot.userHandle)
- var notifiedApps: List<CharSequence> = listOf()
- if (featureFlags.isEnabled(Flags.SCREENSHOT_DETECTION)) {
- notifiedApps = screenshotDetectionController.maybeNotifyOfScreenshot(screenshot)
- }
+ var notifiedApps: List<CharSequence> =
+ screenshotDetectionController.maybeNotifyOfScreenshot(screenshot)
// If work profile first run needs to show, bias towards that, otherwise show screenshot
// detection notification if needed.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt
new file mode 100644
index 0000000..d7c29f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.statusbar.notification.icon.ui.viewbinder
+
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.collection.NotifCollection
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.statusbar.ui.SystemBarUtilsState
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.launch
+
+/** Binds a [NotificationIconContainer] to a [NotificationIconContainerAlwaysOnDisplayViewModel]. */
+class NotificationIconContainerAlwaysOnDisplayViewBinder
+@Inject
+constructor(
+ private val viewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
+ private val keyguardRootViewModel: KeyguardRootViewModel,
+ private val configuration: ConfigurationState,
+ private val failureTracker: StatusBarIconViewBindingFailureTracker,
+ private val screenOffAnimationController: ScreenOffAnimationController,
+ private val systemBarUtilsState: SystemBarUtilsState,
+ private val viewStore: AlwaysOnDisplayNotificationIconViewStore,
+) {
+ fun bindWhileAttached(view: NotificationIconContainer): DisposableHandle {
+ return view.repeatWhenAttached {
+ lifecycleScope.launch {
+ launch {
+ NotificationIconContainerViewBinder.bind(
+ view = view,
+ viewModel = viewModel,
+ configuration = configuration,
+ systemBarUtilsState = systemBarUtilsState,
+ failureTracker = failureTracker,
+ viewStore = viewStore,
+ )
+ }
+ launch {
+ KeyguardRootViewBinder.bindAodNotifIconVisibility(
+ view = view,
+ isVisible = keyguardRootViewModel.isNotifIconContainerVisible,
+ configuration = configuration,
+ screenOffAnimationController = screenOffAnimationController,
+ )
+ }
+ }
+ }
+ }
+}
+
+/** [IconViewStore] for the always-on display. */
+class AlwaysOnDisplayNotificationIconViewStore
+@Inject
+constructor(notifCollection: NotifCollection) :
+ IconViewStore by (notifCollection.iconViewStoreBy { it.aodIcon })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt
new file mode 100644
index 0000000..783488af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.statusbar.notification.icon.ui.viewbinder
+
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.statusbar.notification.collection.NotifCollection
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.bindIcons
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.ui.SystemBarUtilsState
+import javax.inject.Inject
+
+/** Binds a [NotificationIconContainer] to a [NotificationIconContainerShelfViewModel]. */
+class NotificationIconContainerShelfViewBinder
+@Inject
+constructor(
+ private val viewModel: NotificationIconContainerShelfViewModel,
+ private val configuration: ConfigurationState,
+ private val systemBarUtilsState: SystemBarUtilsState,
+ private val failureTracker: StatusBarIconViewBindingFailureTracker,
+ private val viewStore: ShelfNotificationIconViewStore,
+) {
+ suspend fun bind(view: NotificationIconContainer) {
+ viewModel.icons.bindIcons(
+ view,
+ configuration,
+ systemBarUtilsState,
+ notifyBindingFailures = { failureTracker.shelfFailures = it },
+ viewStore,
+ )
+ }
+}
+
+/** [IconViewStore] for the [com.android.systemui.statusbar.NotificationShelf] */
+class ShelfNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) :
+ IconViewStore by (notifCollection.iconViewStoreBy { it.shelfIcon })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt
new file mode 100644
index 0000000..8e089b1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt
@@ -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.statusbar.notification.icon.ui.viewbinder
+
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.collection.NotifCollection
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.ui.SystemBarUtilsState
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.launch
+
+/** Binds a [NotificationIconContainer] to a [NotificationIconContainerStatusBarViewModel]. */
+class NotificationIconContainerStatusBarViewBinder
+@Inject
+constructor(
+ private val viewModel: NotificationIconContainerStatusBarViewModel,
+ private val configuration: ConfigurationState,
+ private val systemBarUtilsState: SystemBarUtilsState,
+ private val failureTracker: StatusBarIconViewBindingFailureTracker,
+ private val viewStore: StatusBarNotificationIconViewStore,
+) {
+ fun bindWhileAttached(view: NotificationIconContainer): DisposableHandle {
+ return view.repeatWhenAttached {
+ lifecycleScope.launch {
+ NotificationIconContainerViewBinder.bind(
+ view = view,
+ viewModel = viewModel,
+ configuration = configuration,
+ systemBarUtilsState = systemBarUtilsState,
+ failureTracker = failureTracker,
+ viewStore = viewStore,
+ )
+ }
+ }
+ }
+}
+
+/** [IconViewStore] for the status bar. */
+class StatusBarNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) :
+ IconViewStore by (notifCollection.iconViewStoreBy { it.statusBarIcon })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index e1e30e1..8fe0022 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -35,7 +35,6 @@
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconColors
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData.LimitType
@@ -45,7 +44,6 @@
import com.android.systemui.util.ui.isAnimating
import com.android.systemui.util.ui.stopAnimating
import com.android.systemui.util.ui.value
-import javax.inject.Inject
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
@@ -56,42 +54,6 @@
/** Binds a view-model to a [NotificationIconContainer]. */
object NotificationIconContainerViewBinder {
- @JvmStatic
- fun bindWhileAttached(
- view: NotificationIconContainer,
- viewModel: NotificationIconContainerShelfViewModel,
- configuration: ConfigurationState,
- systemBarUtilsState: SystemBarUtilsState,
- failureTracker: StatusBarIconViewBindingFailureTracker,
- viewStore: IconViewStore,
- ): DisposableHandle {
- return view.repeatWhenAttached {
- lifecycleScope.launch {
- viewModel.icons.bindIcons(
- view,
- configuration,
- systemBarUtilsState,
- notifyBindingFailures = { failureTracker.shelfFailures = it },
- viewStore,
- )
- }
- }
- }
-
- @JvmStatic
- fun bindWhileAttached(
- view: NotificationIconContainer,
- viewModel: NotificationIconContainerStatusBarViewModel,
- configuration: ConfigurationState,
- systemBarUtilsState: SystemBarUtilsState,
- failureTracker: StatusBarIconViewBindingFailureTracker,
- viewStore: IconViewStore,
- ): DisposableHandle =
- view.repeatWhenAttached {
- lifecycleScope.launch {
- bind(view, viewModel, configuration, systemBarUtilsState, failureTracker, viewStore)
- }
- }
suspend fun bind(
view: NotificationIconContainer,
@@ -215,7 +177,7 @@
* given `iconKey`. The parent [Job] of this coroutine will be cancelled automatically when the
* view is to be unbound.
*/
- private suspend fun Flow<NotificationIconsViewData>.bindIcons(
+ suspend fun Flow<NotificationIconsViewData>.bindIcons(
view: NotificationIconContainer,
configuration: ConfigurationState,
systemBarUtilsState: SystemBarUtilsState,
@@ -377,24 +339,14 @@
}
@ColorInt private const val DEFAULT_AOD_ICON_COLOR = Color.WHITE
- private const val TAG = "NotifIconContainerViewBinder"
+ private const val TAG = "NotifIconContainerViewBinder"
}
-/** [IconViewStore] for the [com.android.systemui.statusbar.NotificationShelf] */
-class ShelfNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) :
- IconViewStore by (notifCollection.iconViewStoreBy { it.shelfIcon })
-
-/** [IconViewStore] for the always-on display. */
-class AlwaysOnDisplayNotificationIconViewStore
-@Inject
-constructor(notifCollection: NotifCollection) :
- IconViewStore by (notifCollection.iconViewStoreBy { it.aodIcon })
-
-/** [IconViewStore] for the status bar. */
-class StatusBarNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) :
- IconViewStore by (notifCollection.iconViewStoreBy { it.statusBarIcon })
-
-private fun NotifCollection.iconViewStoreBy(block: (IconPack) -> StatusBarIconView?) =
+/**
+ * Convenience builder for [IconViewStore] that uses [block] to extract the relevant
+ * [StatusBarIconView] from an [IconPack] stored inside of the [NotifCollection].
+ */
+fun NotifCollection.iconViewStoreBy(block: (IconPack) -> StatusBarIconView?) =
IconViewStore { key ->
getEntry(key)?.icons?.let(block)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
index 699e140..5ab4d4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -16,60 +16,38 @@
package com.android.systemui.statusbar.notification.shelf.ui.viewbinder
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.common.ui.ConfigurationState
-import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.NotificationShelf
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder
import com.android.systemui.statusbar.notification.row.ui.viewbinder.ActivatableNotificationViewBinder
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
import com.android.systemui.statusbar.phone.NotificationIconAreaController
-import com.android.systemui.statusbar.ui.SystemBarUtilsState
import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
/** Binds a [NotificationShelf] to its [view model][NotificationShelfViewModel]. */
object NotificationShelfViewBinder {
- fun bind(
+ suspend fun bind(
shelf: NotificationShelf,
viewModel: NotificationShelfViewModel,
- configuration: ConfigurationState,
- systemBarUtilsState: SystemBarUtilsState,
falsingManager: FalsingManager,
- iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
+ nicBinder: NotificationIconContainerShelfViewBinder,
notificationIconAreaController: NotificationIconAreaController,
- shelfIconViewStore: ShelfNotificationIconViewStore,
- ) {
+ ): Unit = coroutineScope {
ActivatableNotificationViewBinder.bind(viewModel, shelf, falsingManager)
shelf.apply {
if (NotificationIconContainerRefactor.isEnabled) {
- NotificationIconContainerViewBinder.bindWhileAttached(
- shelfIcons,
- viewModel.icons,
- configuration,
- systemBarUtilsState,
- iconViewBindingFailureTracker,
- shelfIconViewStore,
- )
+ launch { nicBinder.bind(shelfIcons) }
} else {
notificationIconAreaController.setShelfIcons(shelfIcons)
}
- repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
- viewModel.canModifyColorOfNotifications.collect(
- ::setCanModifyColorOfNotifications
- )
- }
- launch { viewModel.isClickable.collect(::setCanInteract) }
- registerViewListenersWhileAttached(shelf, viewModel)
- }
+ launch {
+ viewModel.canModifyColorOfNotifications.collect(::setCanModifyColorOfNotifications)
}
+ launch { viewModel.isClickable.collect(::setCanInteract) }
+ registerViewListenersWhileAttached(shelf, viewModel)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
index 64b5b62c..5ca8b53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
@@ -18,7 +18,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.NotificationShelf
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModel
import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
import javax.inject.Inject
@@ -32,7 +31,6 @@
constructor(
private val interactor: NotificationShelfInteractor,
activatableViewModel: ActivatableNotificationViewModel,
- val icons: NotificationIconContainerShelfViewModel,
) : ActivatableNotificationViewModel by activatableViewModel {
/** Is the shelf allowed to be clickable when it has content? */
val isClickable: Flow<Boolean>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index d2fca8f..7c7d943 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -859,7 +859,7 @@
mGroupExpansionManager.registerGroupExpansionChangeListener(
(changedRow, expanded) -> mView.onGroupExpandChanged(changedRow, expanded));
- mViewBinder.bind(mView, this);
+ mViewBinder.bindWhileAttached(mView, this);
if (!FooterViewRefactor.isEnabled()) {
collectFlow(mView, mKeyguardTransitionRepo.getTransitions(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt
index 274bf94..910b40f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt
@@ -16,29 +16,22 @@
package com.android.systemui.statusbar.notification.stack.ui.viewbinder
import androidx.core.view.doOnDetach
-import androidx.lifecycle.lifecycleScope
-import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
-import kotlinx.coroutines.launch
/**
* Binds a [NotificationStackScrollLayoutController] to its [view model][NotificationListViewModel].
*/
object HideNotificationsBinder {
- fun bindHideList(
+ suspend fun bindHideList(
viewController: NotificationStackScrollLayoutController,
viewModel: NotificationListViewModel
) {
- viewController.view.repeatWhenAttached {
- lifecycleScope.launch {
- viewModel.hideListViewModel.shouldHideListForPerformance.collect { shouldHide ->
- viewController.bindHideState(shouldHide)
- }
- }
- }
-
viewController.view.doOnDetach { viewController.bindHideState(shouldHide = false) }
+
+ viewModel.hideListViewModel.shouldHideListForPerformance.collect { shouldHide ->
+ viewController.bindHideState(shouldHide)
+ }
}
private fun NotificationStackScrollLayoutController.bindHideState(shouldHide: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 9373d49..1b36660 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -32,15 +32,14 @@
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder
import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.HideNotificationsBinder.bindHideList
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
import com.android.systemui.statusbar.phone.NotificationIconAreaController
-import com.android.systemui.statusbar.ui.SystemBarUtilsState
+import com.android.systemui.util.kotlin.getOrNull
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.combine
@@ -55,25 +54,27 @@
private val configuration: ConfigurationState,
private val falsingManager: FalsingManager,
private val iconAreaController: NotificationIconAreaController,
- private val iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
private val metricsLogger: MetricsLogger,
- private val shelfIconViewStore: ShelfNotificationIconViewStore,
- private val systemBarUtilsState: SystemBarUtilsState,
+ private val nicBinder: NotificationIconContainerShelfViewBinder,
) {
- fun bind(
+ fun bindWhileAttached(
view: NotificationStackScrollLayout,
viewController: NotificationStackScrollLayoutController
) {
- bindShelf(view)
- bindHideList(viewController, viewModel)
+ val shelf =
+ LayoutInflater.from(view.context)
+ .inflate(R.layout.status_bar_notification_shelf, view, false) as NotificationShelf
+ view.setShelf(shelf)
- if (FooterViewRefactor.isEnabled) {
- bindFooter(view)
- bindEmptyShade(view)
+ view.repeatWhenAttached {
+ lifecycleScope.launch {
+ launch { bindShelf(shelf) }
+ launch { bindHideList(viewController, viewModel) }
- view.repeatWhenAttached {
- lifecycleScope.launch {
+ if (FooterViewRefactor.isEnabled) {
+ launch { bindFooter(view) }
+ launch { bindEmptyShade(view) }
viewModel.isImportantForAccessibility.collect { isImportantForAccessibility ->
view.setImportantForAccessibilityYesNo(isImportantForAccessibility)
}
@@ -82,73 +83,57 @@
}
}
- private fun bindShelf(parentView: NotificationStackScrollLayout) {
- val shelf =
- LayoutInflater.from(parentView.context)
- .inflate(R.layout.status_bar_notification_shelf, parentView, false)
- as NotificationShelf
+ private suspend fun bindShelf(shelf: NotificationShelf) {
NotificationShelfViewBinder.bind(
shelf,
viewModel.shelf,
- configuration,
- systemBarUtilsState,
falsingManager,
- iconViewBindingFailureTracker,
+ nicBinder,
iconAreaController,
- shelfIconViewStore,
)
- parentView.setShelf(shelf)
}
- private fun bindFooter(parentView: NotificationStackScrollLayout) {
- viewModel.footer.ifPresent { footerViewModel ->
+ private suspend fun bindFooter(parentView: NotificationStackScrollLayout) {
+ viewModel.footer.getOrNull()?.let { footerViewModel ->
// The footer needs to be re-inflated every time the theme or the font size changes.
- parentView.repeatWhenAttached {
- configuration.reinflateAndBindLatest(
- R.layout.status_bar_notification_footer,
- parentView,
- attachToRoot = false,
- backgroundDispatcher,
- ) { footerView: FooterView ->
- traceSection("bind FooterView") {
- val disposableHandle =
- FooterViewBinder.bind(
- footerView,
- footerViewModel,
- clearAllNotifications = {
- metricsLogger.action(
- MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES
- )
- parentView.clearAllNotifications()
- },
- )
- parentView.setFooterView(footerView)
- return@reinflateAndBindLatest disposableHandle
- }
+ configuration.reinflateAndBindLatest(
+ R.layout.status_bar_notification_footer,
+ parentView,
+ attachToRoot = false,
+ backgroundDispatcher,
+ ) { footerView: FooterView ->
+ traceSection("bind FooterView") {
+ val disposableHandle =
+ FooterViewBinder.bind(
+ footerView,
+ footerViewModel,
+ clearAllNotifications = {
+ metricsLogger.action(
+ MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES
+ )
+ parentView.clearAllNotifications()
+ },
+ )
+ parentView.setFooterView(footerView)
+ return@reinflateAndBindLatest disposableHandle
}
}
}
}
- private fun bindEmptyShade(
- parentView: NotificationStackScrollLayout,
- ) {
- parentView.repeatWhenAttached {
- lifecycleScope.launch {
- combine(
- viewModel.shouldShowEmptyShadeView,
- viewModel.areNotificationsHiddenInShade,
- viewModel.hasFilteredOutSeenNotifications,
- ::Triple
- )
- .collect { (shouldShow, areNotifsHidden, hasFilteredNotifs) ->
- parentView.updateEmptyShadeView(
- shouldShow,
- areNotifsHidden,
- hasFilteredNotifs,
- )
- }
+ private suspend fun bindEmptyShade(parentView: NotificationStackScrollLayout) {
+ combine(
+ viewModel.shouldShowEmptyShadeView,
+ viewModel.areNotificationsHiddenInShade,
+ viewModel.hasFilteredOutSeenNotifications,
+ ::Triple
+ )
+ .collect { (shouldShow, areNotifsHidden, hasFilteredNotifs) ->
+ parentView.updateEmptyShadeView(
+ shouldShow,
+ areNotifsHidden,
+ hasFilteredNotifs,
+ )
}
- }
}
}
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 cd99934..2740cc6 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
@@ -38,7 +38,6 @@
import com.android.app.animation.InterpolatorsAndroidX;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
-import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
@@ -54,10 +53,7 @@
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState;
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore;
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder;
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
@@ -75,7 +71,6 @@
import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.ui.SystemBarUtilsState;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
import com.android.systemui.util.CarrierConfigTracker;
@@ -95,6 +90,8 @@
import kotlin.Unit;
+import kotlinx.coroutines.DisposableHandle;
+
/**
* Contains the collapsed status bar and handles hiding/showing based on disable flags
* and keyguard state. Also manages lifecycle to make sure the views it contains are being
@@ -151,10 +148,7 @@
private final DumpManager mDumpManager;
private final StatusBarWindowStateController mStatusBarWindowStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final NotificationIconContainerStatusBarViewModel mStatusBarIconsViewModel;
- private final ConfigurationState mConfigurationState;
- private final SystemBarUtilsState mSystemBarUtilsState;
- private final StatusBarNotificationIconViewStore mStatusBarIconViewStore;
+ private final NotificationIconContainerStatusBarViewBinder mNicViewBinder;
private final DemoModeController mDemoModeController;
private List<String> mBlockedIcons = new ArrayList<>();
@@ -216,7 +210,7 @@
mWaitingForWindowStateChangeAfterCameraLaunch = false;
mTransitionFromLockscreenToDreamStarted = false;
};
- private final StatusBarIconViewBindingFailureTracker mIconViewBindingFailureTracker;
+ private DisposableHandle mNicBindingDisposable;
@Inject
public CollapsedStatusBarFragment(
@@ -234,7 +228,7 @@
KeyguardStateController keyguardStateController,
ShadeViewController shadeViewController,
StatusBarStateController statusBarStateController,
- StatusBarIconViewBindingFailureTracker iconViewBindingFailureTracker,
+ NotificationIconContainerStatusBarViewBinder nicViewBinder,
CommandQueue commandQueue,
CarrierConfigTracker carrierConfigTracker,
CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
@@ -244,10 +238,6 @@
DumpManager dumpManager,
StatusBarWindowStateController statusBarWindowStateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- NotificationIconContainerStatusBarViewModel statusBarIconsViewModel,
- ConfigurationState configurationState,
- SystemBarUtilsState systemBarUtilsState,
- StatusBarNotificationIconViewStore statusBarIconViewStore,
DemoModeController demoModeController) {
mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
mOngoingCallController = ongoingCallController;
@@ -263,7 +253,7 @@
mKeyguardStateController = keyguardStateController;
mShadeViewController = shadeViewController;
mStatusBarStateController = statusBarStateController;
- mIconViewBindingFailureTracker = iconViewBindingFailureTracker;
+ mNicViewBinder = nicViewBinder;
mCommandQueue = commandQueue;
mCarrierConfigTracker = carrierConfigTracker;
mCollapsedStatusBarFragmentLogger = collapsedStatusBarFragmentLogger;
@@ -273,10 +263,6 @@
mDumpManager = dumpManager;
mStatusBarWindowStateController = statusBarWindowStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mStatusBarIconsViewModel = statusBarIconsViewModel;
- mConfigurationState = configurationState;
- mSystemBarUtilsState = systemBarUtilsState;
- mStatusBarIconViewStore = statusBarIconViewStore;
mDemoModeController = demoModeController;
}
@@ -455,6 +441,12 @@
mStartableStates.put(startable, Startable.State.STOPPED);
}
mDumpManager.unregisterDumpable(getClass().getSimpleName());
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ if (mNicBindingDisposable != null) {
+ mNicBindingDisposable.dispose();
+ mNicBindingDisposable = null;
+ }
+ }
}
/** Initializes views related to the notification icon area. */
@@ -466,13 +458,7 @@
.inflate(R.layout.notification_icon_area, notificationIconArea, true);
NotificationIconContainer notificationIcons =
notificationIconArea.requireViewById(R.id.notificationIcons);
- NotificationIconContainerViewBinder.bindWhileAttached(
- notificationIcons,
- mStatusBarIconsViewModel,
- mConfigurationState,
- mSystemBarUtilsState,
- mIconViewBindingFailureTracker,
- mStatusBarIconViewStore);
+ mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons);
} else {
mNotificationIconAreaInner =
mNotificationIconAreaController.getNotificationInnerAreaView();
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
index e0d205f..c170eb5 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
@@ -37,6 +37,7 @@
import com.android.internal.util.UserIcons
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.Flags.switchUserOnBg
import com.android.systemui.SystemUISecondaryUserService
import com.android.systemui.animation.Expandable
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -44,6 +45,7 @@
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.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -100,6 +102,7 @@
broadcastDispatcher: BroadcastDispatcher,
keyguardUpdateMonitor: KeyguardUpdateMonitor,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Main private val mainDispatcher: CoroutineDispatcher,
private val activityManager: ActivityManager,
private val refreshUsersScheduler: RefreshUsersScheduler,
private val guestUserInteractor: GuestUserInteractor,
@@ -339,7 +342,11 @@
}
.launchIn(applicationScope)
restartSecondaryService(repository.getSelectedUserInfo().id)
- keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+ applicationScope.launch {
+ withContext(mainDispatcher) {
+ keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+ }
+ }
}
fun addCallback(callback: UserCallback) {
@@ -593,10 +600,18 @@
private fun switchUser(userId: Int) {
// TODO(b/246631653): track jank and latency like in the old impl.
refreshUsersScheduler.pause()
- try {
- activityManager.switchUser(userId)
- } catch (e: RemoteException) {
- Log.e(TAG, "Couldn't switch user.", e)
+ val runnable = Runnable {
+ try {
+ activityManager.switchUser(userId)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Couldn't switch user.", e)
+ }
+ }
+
+ if (switchUserOnBg()) {
+ applicationScope.launch { withContext(backgroundDispatcher) { runnable.run() } }
+ } else {
+ runnable.run()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java b/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java
deleted file mode 100644
index 8215360..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.util.leak;
-
-import android.content.ClipData;
-import android.content.ClipDescription;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Build;
-import android.util.Log;
-
-import androidx.core.content.FileProvider;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-
-/**
- * Utility class for dumping, compressing, sending, and serving heap dump files.
- *
- * <p>Unlike the Internet, this IS a big truck you can dump something on.
- */
-public class DumpTruck {
- private static final String FILEPROVIDER_AUTHORITY = "com.android.systemui.fileprovider";
- private static final String FILEPROVIDER_PATH = "leak";
-
- private static final String TAG = "DumpTruck";
- private static final int BUFSIZ = 1024 * 1024; // 1MB
-
- private final Context context;
- private final GarbageMonitor mGarbageMonitor;
- private Uri hprofUri;
- private long rss;
- final StringBuilder body = new StringBuilder();
-
- public DumpTruck(Context context, GarbageMonitor garbageMonitor) {
- this.context = context;
- mGarbageMonitor = garbageMonitor;
- }
-
- /**
- * Capture memory for the given processes and zip them up for sharing.
- *
- * @param pids
- * @return this, for chaining
- */
- public DumpTruck captureHeaps(List<Long> pids) {
- final File dumpDir = new File(context.getCacheDir(), FILEPROVIDER_PATH);
- dumpDir.mkdirs();
- hprofUri = null;
-
- body.setLength(0);
- body.append("Build: ").append(Build.DISPLAY).append("\n\nProcesses:\n");
-
- final ArrayList<String> paths = new ArrayList<String>();
- final int myPid = android.os.Process.myPid();
-
- for (Long pidL : pids) {
- final int pid = pidL.intValue();
- body.append(" pid ").append(pid);
- GarbageMonitor.ProcessMemInfo info = mGarbageMonitor.getMemInfo(pid);
- if (info != null) {
- body.append(":")
- .append(" up=")
- .append(info.getUptime())
- .append(" rss=")
- .append(info.currentRss);
- rss = info.currentRss;
- }
- if (pid == myPid) {
- final String path =
- new File(dumpDir, String.format("heap-%d.ahprof", pid)).getPath();
- Log.v(TAG, "Dumping memory info for process " + pid + " to " + path);
- try {
- android.os.Debug.dumpHprofData(path); // will block
- paths.add(path);
- body.append(" (hprof attached)");
- } catch (IOException e) {
- Log.e(TAG, "error dumping memory:", e);
- body.append("\n** Could not dump heap: \n").append(e.toString()).append("\n");
- }
- }
- body.append("\n");
- }
-
- try {
- final String zipfile =
- new File(dumpDir, String.format("hprof-%d.zip", System.currentTimeMillis()))
- .getCanonicalPath();
- if (DumpTruck.zipUp(zipfile, paths)) {
- final File pathFile = new File(zipfile);
- hprofUri = FileProvider.getUriForFile(context, FILEPROVIDER_AUTHORITY, pathFile);
- Log.v(TAG, "Heap dump accessible at URI: " + hprofUri);
- }
- } catch (IOException e) {
- Log.e(TAG, "unable to zip up heapdumps", e);
- body.append("\n** Could not zip up files: \n").append(e.toString()).append("\n");
- }
-
- return this;
- }
-
- /**
- * Get the Uri of the current heap dump. Be sure to call captureHeaps first.
- *
- * @return Uri to the dump served by the SystemUI file provider
- */
- public Uri getDumpUri() {
- return hprofUri;
- }
-
- /**
- * Get an ACTION_SEND intent suitable for startActivity() or attaching to a Notification.
- *
- * @return share intent
- */
- public Intent createShareIntent() {
- Intent shareIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
- shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- shareIntent.putExtra(Intent.EXTRA_SUBJECT,
- String.format("SystemUI memory dump (rss=%dM)", rss / 1024));
-
- shareIntent.putExtra(Intent.EXTRA_TEXT, body.toString());
-
- if (hprofUri != null) {
- final ArrayList<Uri> uriList = new ArrayList<>();
- uriList.add(hprofUri);
- shareIntent.setType("application/zip");
- shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriList);
-
- // Include URI in ClipData also, so that grantPermission picks it up.
- // We don't use setData here because some apps interpret this as "to:".
- ClipData clipdata = new ClipData(new ClipDescription("content",
- new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
- new ClipData.Item(hprofUri));
- shareIntent.setClipData(clipdata);
- shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- }
- return shareIntent;
- }
-
- private static boolean zipUp(String zipfilePath, ArrayList<String> paths) {
- try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipfilePath))) {
- final byte[] buf = new byte[BUFSIZ];
-
- for (String filename : paths) {
- try (InputStream is = new BufferedInputStream(new FileInputStream(filename))) {
- ZipEntry entry = new ZipEntry(filename);
- zos.putNextEntry(entry);
- int len;
- while (0 < (len = is.read(buf, 0, BUFSIZ))) {
- zos.write(buf, 0, len);
- }
- zos.closeEntry();
- }
- }
- return true;
- } catch (IOException e) {
- Log.e(TAG, "error zipping up profile data", e);
- }
- return false;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
deleted file mode 100644
index de392d3..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ /dev/null
@@ -1,617 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.util.leak;
-
-import static android.service.quicksettings.Tile.STATE_ACTIVE;
-import static android.telephony.ims.feature.ImsFeature.STATE_UNAVAILABLE;
-
-import static com.android.internal.logging.MetricsLogger.VIEW_UNKNOWN;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.ColorStateList;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Process;
-import android.os.SystemProperties;
-import android.provider.Settings;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.util.LongSparseArray;
-import android.view.View;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.CoreStartable;
-import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.QsEventLogger;
-import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
-import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.concurrency.MessageRouter;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-
-/**
- * Suite of tools to periodically inspect the System UI heap and possibly prompt the user to
- * capture heap dumps and report them. Includes the implementation of the "Dump SysUI Heap"
- * quick settings tile.
- */
-@SysUISingleton
-public class GarbageMonitor implements Dumpable {
- // Feature switches
- // ================
-
- // Whether to use TrackedGarbage to trigger LeakReporter. Off by default unless you set the
- // appropriate sysprop on a userdebug device.
- public static final boolean LEAK_REPORTING_ENABLED = Build.IS_DEBUGGABLE
- && SystemProperties.getBoolean("debug.enable_leak_reporting", false);
- public static final String FORCE_ENABLE_LEAK_REPORTING = "sysui_force_enable_leak_reporting";
-
- // Heap tracking: watch the current memory levels and update the MemoryTile if available.
- // On for all userdebug devices.
- public static final boolean HEAP_TRACKING_ENABLED = Build.IS_DEBUGGABLE;
-
- // Tell QSTileHost.java to toss this into the default tileset?
- public static final boolean ADD_MEMORY_TILE_TO_DEFAULT_ON_DEBUGGABLE_BUILDS = true;
-
- // whether to use ActivityManager.setHeapLimit (and post a notification to the user asking
- // to dump the heap). Off by default unless you set the appropriate sysprop on userdebug
- private static final boolean ENABLE_AM_HEAP_LIMIT = Build.IS_DEBUGGABLE
- && SystemProperties.getBoolean("debug.enable_sysui_heap_limit", false);
-
- // Tuning params
- // =============
-
- // threshold for setHeapLimit(), in KB (overrides R.integer.watch_heap_limit)
- private static final String SETTINGS_KEY_AM_HEAP_LIMIT = "systemui_am_heap_limit";
-
- private static final long GARBAGE_INSPECTION_INTERVAL =
- 15 * DateUtils.MINUTE_IN_MILLIS; // 15 min
- private static final long HEAP_TRACK_INTERVAL = 1 * DateUtils.MINUTE_IN_MILLIS; // 1 min
- private static final int HEAP_TRACK_HISTORY_LEN = 720; // 12 hours
-
- private static final int DO_GARBAGE_INSPECTION = 1000;
- private static final int DO_HEAP_TRACK = 3000;
-
- static final int GARBAGE_ALLOWANCE = 5;
-
- private static final String TAG = "GarbageMonitor";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private final MessageRouter mMessageRouter;
- private final TrackedGarbage mTrackedGarbage;
- private final LeakReporter mLeakReporter;
- private final Context mContext;
- private final DelayableExecutor mDelayableExecutor;
- private MemoryTile mQSTile;
- private final DumpTruck mDumpTruck;
-
- private final LongSparseArray<ProcessMemInfo> mData = new LongSparseArray<>();
- private final ArrayList<Long> mPids = new ArrayList<>();
-
- private long mHeapLimit;
-
- /**
- */
- @Inject
- public GarbageMonitor(
- Context context,
- @Background DelayableExecutor delayableExecutor,
- @Background MessageRouter messageRouter,
- LeakDetector leakDetector,
- LeakReporter leakReporter,
- DumpManager dumpManager) {
- mContext = context.getApplicationContext();
-
- mDelayableExecutor = delayableExecutor;
- mMessageRouter = messageRouter;
- mMessageRouter.subscribeTo(DO_GARBAGE_INSPECTION, this::doGarbageInspection);
- mMessageRouter.subscribeTo(DO_HEAP_TRACK, this::doHeapTrack);
-
- mTrackedGarbage = leakDetector.getTrackedGarbage();
- mLeakReporter = leakReporter;
-
- mDumpTruck = new DumpTruck(mContext, this);
-
- dumpManager.registerDumpable(getClass().getSimpleName(), this);
-
- if (ENABLE_AM_HEAP_LIMIT) {
- mHeapLimit = Settings.Global.getInt(context.getContentResolver(),
- SETTINGS_KEY_AM_HEAP_LIMIT,
- mContext.getResources().getInteger(R.integer.watch_heap_limit));
- }
- }
-
- public void startLeakMonitor() {
- if (mTrackedGarbage == null) {
- return;
- }
-
- mMessageRouter.sendMessage(DO_GARBAGE_INSPECTION);
- }
-
- public void startHeapTracking() {
- startTrackingProcess(
- android.os.Process.myPid(), mContext.getPackageName(), System.currentTimeMillis());
- mMessageRouter.sendMessage(DO_HEAP_TRACK);
- }
-
- private boolean gcAndCheckGarbage() {
- if (mTrackedGarbage.countOldGarbage() > GARBAGE_ALLOWANCE) {
- Runtime.getRuntime().gc();
- return true;
- }
- return false;
- }
-
- void reinspectGarbageAfterGc() {
- int count = mTrackedGarbage.countOldGarbage();
- if (count > GARBAGE_ALLOWANCE) {
- mLeakReporter.dumpLeak(count);
- }
- }
-
- public ProcessMemInfo getMemInfo(int pid) {
- return mData.get(pid);
- }
-
- public List<Long> getTrackedProcesses() {
- return mPids;
- }
-
- public void startTrackingProcess(long pid, String name, long start) {
- synchronized (mPids) {
- if (mPids.contains(pid)) return;
-
- mPids.add(pid);
- logPids();
-
- mData.put(pid, new ProcessMemInfo(pid, name, start));
- }
- }
-
- private void logPids() {
- if (DEBUG) {
- StringBuffer sb = new StringBuffer("Now tracking processes: ");
- for (int i = 0; i < mPids.size(); i++) {
- final int p = mPids.get(i).intValue();
- sb.append(" ");
- }
- Log.v(TAG, sb.toString());
- }
- }
-
- private void update() {
- synchronized (mPids) {
- for (int i = 0; i < mPids.size(); i++) {
- final int pid = mPids.get(i).intValue();
- // rssValues contains [VmRSS, RssFile, RssAnon, VmSwap].
- long[] rssValues = Process.getRss(pid);
- if (rssValues == null && rssValues.length == 0) {
- if (DEBUG) Log.e(TAG, "update: Process.getRss() didn't provide any values.");
- break;
- }
- long rss = rssValues[0];
- final ProcessMemInfo info = mData.get(pid);
- info.rss[info.head] = info.currentRss = rss;
- info.head = (info.head + 1) % info.rss.length;
- if (info.currentRss > info.max) info.max = info.currentRss;
- if (info.currentRss == 0) {
- if (DEBUG) Log.v(TAG, "update: pid " + pid + " has rss=0, it probably died");
- mData.remove(pid);
- }
- }
- for (int i = mPids.size() - 1; i >= 0; i--) {
- final long pid = mPids.get(i).intValue();
- if (mData.get(pid) == null) {
- mPids.remove(i);
- logPids();
- }
- }
- }
- if (mQSTile != null) mQSTile.update();
- }
-
- private void setTile(MemoryTile tile) {
- mQSTile = tile;
- if (tile != null) tile.update();
- }
-
- private static String formatBytes(long b) {
- String[] SUFFIXES = {"B", "K", "M", "G", "T"};
- int i;
- for (i = 0; i < SUFFIXES.length; i++) {
- if (b < 1024) break;
- b /= 1024;
- }
- return b + SUFFIXES[i];
- }
-
- private Intent dumpHprofAndGetShareIntent() {
- return mDumpTruck.captureHeaps(getTrackedProcesses()).createShareIntent();
- }
-
- @Override
- public void dump(PrintWriter pw, @Nullable String[] args) {
- pw.println("GarbageMonitor params:");
- pw.println(String.format(" mHeapLimit=%d KB", mHeapLimit));
- pw.println(String.format(" GARBAGE_INSPECTION_INTERVAL=%d (%.1f mins)",
- GARBAGE_INSPECTION_INTERVAL,
- (float) GARBAGE_INSPECTION_INTERVAL / DateUtils.MINUTE_IN_MILLIS));
- final float htiMins = HEAP_TRACK_INTERVAL / DateUtils.MINUTE_IN_MILLIS;
- pw.println(String.format(" HEAP_TRACK_INTERVAL=%d (%.1f mins)",
- HEAP_TRACK_INTERVAL,
- htiMins));
- pw.println(String.format(" HEAP_TRACK_HISTORY_LEN=%d (%.1f hr total)",
- HEAP_TRACK_HISTORY_LEN,
- (float) HEAP_TRACK_HISTORY_LEN * htiMins / 60f));
-
- pw.println("GarbageMonitor tracked processes:");
-
- for (long pid : mPids) {
- final ProcessMemInfo pmi = mData.get(pid);
- if (pmi != null) {
- pmi.dump(pw, args);
- }
- }
- }
-
-
- private static class MemoryIconDrawable extends Drawable {
- long rss, limit;
- final Drawable baseIcon;
- final Paint paint = new Paint();
- final float dp;
-
- MemoryIconDrawable(Context context) {
- baseIcon = context.getDrawable(R.drawable.ic_memory).mutate();
- dp = context.getResources().getDisplayMetrics().density;
- paint.setColor(Color.WHITE);
- }
-
- public void setRss(long rss) {
- if (rss != this.rss) {
- this.rss = rss;
- invalidateSelf();
- }
- }
-
- public void setLimit(long limit) {
- if (limit != this.limit) {
- this.limit = limit;
- invalidateSelf();
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- baseIcon.draw(canvas);
-
- if (limit > 0 && rss > 0) {
- float frac = Math.min(1f, (float) rss / limit);
-
- final Rect bounds = getBounds();
- canvas.translate(bounds.left + 8 * dp, bounds.top + 5 * dp);
- //android:pathData="M16.0,5.0l-8.0,0.0l0.0,14.0l8.0,0.0z"
- canvas.drawRect(0, 14 * dp * (1 - frac), 8 * dp + 1, 14 * dp + 1, paint);
- }
- }
-
- @Override
- public void setBounds(int left, int top, int right, int bottom) {
- super.setBounds(left, top, right, bottom);
- baseIcon.setBounds(left, top, right, bottom);
- }
-
- @Override
- public int getIntrinsicHeight() {
- return baseIcon.getIntrinsicHeight();
- }
-
- @Override
- public int getIntrinsicWidth() {
- return baseIcon.getIntrinsicWidth();
- }
-
- @Override
- public void setAlpha(int i) {
- baseIcon.setAlpha(i);
- }
-
- @Override
- public void setColorFilter(ColorFilter colorFilter) {
- baseIcon.setColorFilter(colorFilter);
- paint.setColorFilter(colorFilter);
- }
-
- @Override
- public void setTint(int tint) {
- super.setTint(tint);
- baseIcon.setTint(tint);
- }
-
- @Override
- public void setTintList(ColorStateList tint) {
- super.setTintList(tint);
- baseIcon.setTintList(tint);
- }
-
- @Override
- public void setTintMode(PorterDuff.Mode tintMode) {
- super.setTintMode(tintMode);
- baseIcon.setTintMode(tintMode);
- }
-
- @Override
- public int getOpacity() {
- return PixelFormat.TRANSLUCENT;
- }
- }
-
- private static class MemoryGraphIcon extends QSTile.Icon {
- long rss, limit;
-
- public void setRss(long rss) {
- this.rss = rss;
- }
-
- public void setHeapLimit(long limit) {
- this.limit = limit;
- }
-
- @Override
- public Drawable getDrawable(Context context) {
- final MemoryIconDrawable drawable = new MemoryIconDrawable(context);
- drawable.setRss(rss);
- drawable.setLimit(limit);
- return drawable;
- }
- }
-
- public static class MemoryTile extends QSTileImpl<QSTile.State> {
- public static final String TILE_SPEC = "dbg:mem";
-
- private final GarbageMonitor gm;
- private ProcessMemInfo pmi;
- private boolean dumpInProgress;
- private final PanelInteractor mPanelInteractor;
-
- @Inject
- public MemoryTile(
- QSHost host,
- QsEventLogger uiEventLogger,
- @Background Looper backgroundLooper,
- @Main Handler mainHandler,
- FalsingManager falsingManager,
- MetricsLogger metricsLogger,
- StatusBarStateController statusBarStateController,
- ActivityStarter activityStarter,
- QSLogger qsLogger,
- GarbageMonitor monitor,
- PanelInteractor panelInteractor
- ) {
- super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
- statusBarStateController, activityStarter, qsLogger);
- gm = monitor;
- mPanelInteractor = panelInteractor;
- }
-
- @Override
- public State newTileState() {
- return new QSTile.State();
- }
-
- @Override
- public Intent getLongClickIntent() {
- return new Intent();
- }
-
- @Override
- protected void handleClick(@Nullable View view) {
- if (dumpInProgress) return;
-
- dumpInProgress = true;
- refreshState();
- new Thread("HeapDumpThread") {
- @Override
- public void run() {
- try {
- // wait for animations & state changes
- Thread.sleep(500);
- } catch (InterruptedException ignored) { }
- final Intent shareIntent = gm.dumpHprofAndGetShareIntent();
- mHandler.post(() -> {
- dumpInProgress = false;
- refreshState();
- mPanelInteractor.collapsePanels();
- mActivityStarter.postStartActivityDismissingKeyguard(shareIntent, 0);
- });
- }
- }.start();
- }
-
- @Override
- public int getMetricsCategory() {
- return VIEW_UNKNOWN;
- }
-
- @Override
- public void handleSetListening(boolean listening) {
- super.handleSetListening(listening);
- if (gm != null) gm.setTile(listening ? this : null);
-
- final ActivityManager am = mContext.getSystemService(ActivityManager.class);
- if (listening && gm.mHeapLimit > 0) {
- am.setWatchHeapLimit(1024 * gm.mHeapLimit); // why is this in bytes?
- } else {
- am.clearWatchHeapLimit();
- }
- }
-
- @Override
- public CharSequence getTileLabel() {
- return getState().label;
- }
-
- @Override
- protected void handleUpdateState(State state, Object arg) {
- pmi = gm.getMemInfo(Process.myPid());
- final MemoryGraphIcon icon = new MemoryGraphIcon();
- icon.setHeapLimit(gm.mHeapLimit);
- state.state = dumpInProgress ? STATE_UNAVAILABLE : STATE_ACTIVE;
- state.label = dumpInProgress
- ? "Dumping..."
- : mContext.getString(R.string.heap_dump_tile_name);
- if (pmi != null) {
- icon.setRss(pmi.currentRss);
- state.secondaryLabel =
- String.format(
- "rss: %s / %s",
- formatBytes(pmi.currentRss * 1024),
- formatBytes(gm.mHeapLimit * 1024));
- } else {
- icon.setRss(0);
- state.secondaryLabel = null;
- }
- state.icon = icon;
- }
-
- public void update() {
- refreshState();
- }
-
- public long getRss() {
- return pmi != null ? pmi.currentRss : 0;
- }
-
- public long getHeapLimit() {
- return gm != null ? gm.mHeapLimit : 0;
- }
- }
-
- /** */
- public static class ProcessMemInfo implements Dumpable {
- public long pid;
- public String name;
- public long startTime;
- public long currentRss;
- public long[] rss = new long[HEAP_TRACK_HISTORY_LEN];
- public long max = 1;
- public int head = 0;
-
- public ProcessMemInfo(long pid, String name, long start) {
- this.pid = pid;
- this.name = name;
- this.startTime = start;
- }
-
- public long getUptime() {
- return System.currentTimeMillis() - startTime;
- }
-
- @Override
- public void dump(PrintWriter pw, @Nullable String[] args) {
- pw.print("{ \"pid\": ");
- pw.print(pid);
- pw.print(", \"name\": \"");
- pw.print(name.replace('"', '-'));
- pw.print("\", \"start\": ");
- pw.print(startTime);
- pw.print(", \"rss\": [");
- // write rss values starting from the oldest, which is rss[head], wrapping around to
- // rss[(head-1) % rss.length]
- for (int i = 0; i < rss.length; i++) {
- if (i > 0) pw.print(",");
- pw.print(rss[(head + i) % rss.length]);
- }
- pw.println("] }");
- }
- }
-
- /** */
- @SysUISingleton
- public static class Service implements CoreStartable, Dumpable {
- private final Context mContext;
- private final GarbageMonitor mGarbageMonitor;
-
- @Inject
- public Service(Context context, GarbageMonitor garbageMonitor) {
- mContext = context;
- mGarbageMonitor = garbageMonitor;
- }
-
- @Override
- public void start() {
- boolean forceEnable =
- Settings.Secure.getInt(
- mContext.getContentResolver(), FORCE_ENABLE_LEAK_REPORTING, 0)
- != 0;
- if (LEAK_REPORTING_ENABLED || forceEnable) {
- mGarbageMonitor.startLeakMonitor();
- }
- if (HEAP_TRACKING_ENABLED || forceEnable) {
- mGarbageMonitor.startHeapTracking();
- }
- }
-
- @Override
- public void dump(PrintWriter pw, @Nullable String[] args) {
- if (mGarbageMonitor != null) mGarbageMonitor.dump(pw, args);
- }
- }
-
- private void doGarbageInspection(int id) {
- if (gcAndCheckGarbage()) {
- mDelayableExecutor.executeDelayed(this::reinspectGarbageAfterGc, 100);
- }
-
- mMessageRouter.cancelMessages(DO_GARBAGE_INSPECTION);
- mMessageRouter.sendMessageDelayed(DO_GARBAGE_INSPECTION, GARBAGE_INSPECTION_INTERVAL);
- }
-
- private void doHeapTrack(int id) {
- update();
- mMessageRouter.cancelMessages(DO_HEAP_TRACK);
- mMessageRouter.sendMessageDelayed(DO_HEAP_TRACK, HEAP_TRACK_INTERVAL);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitorModule.kt b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitorModule.kt
deleted file mode 100644
index e975200..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitorModule.kt
+++ /dev/null
@@ -1,39 +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.util.leak
-
-import com.android.systemui.CoreStartable
-import com.android.systemui.qs.tileimpl.QSTileImpl
-import dagger.Binds
-import dagger.Module
-import dagger.multibindings.ClassKey
-import dagger.multibindings.IntoMap
-import dagger.multibindings.StringKey
-
-@Module
-interface GarbageMonitorModule {
- /** Inject into GarbageMonitor.Service. */
- @Binds
- @IntoMap
- @ClassKey(GarbageMonitor::class)
- fun bindGarbageMonitorService(sysui: GarbageMonitor.Service): CoreStartable
-
- @Binds
- @IntoMap
- @StringKey(GarbageMonitor.MemoryTile.TILE_SPEC)
- fun bindMemoryTile(memoryTile: GarbageMonitor.MemoryTile): QSTileImpl<*>
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index 88f63ad..a249961 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -34,14 +34,12 @@
import android.widget.RelativeLayout;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager;
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.plugins.clocks.ClockAnimations;
import com.android.systemui.plugins.clocks.ClockController;
@@ -56,14 +54,9 @@
import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker;
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel;
-import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerAlwaysOnDisplayViewBinder;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.ui.SystemBarUtilsState;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
@@ -185,9 +178,7 @@
mKeyguardSliceViewController,
mNotificationIconAreaController,
mSmartspaceController,
- mock(SystemBarUtilsState.class),
- mock(ScreenOffAnimationController.class),
- mock(StatusBarIconViewBindingFailureTracker.class),
+ mock(NotificationIconContainerAlwaysOnDisplayViewBinder.class),
mKeyguardUnlockAnimationController,
mSecureSettings,
mExecutor,
@@ -195,11 +186,6 @@
mDumpManager,
mClockEventController,
mLogBuffer,
- mock(NotificationIconContainerAlwaysOnDisplayViewModel.class),
- mock(KeyguardRootViewModel.class),
- mock(ConfigurationState.class),
- mock(DozeParameters.class),
- mock(AlwaysOnDisplayNotificationIconViewStore.class),
KeyguardInteractorFactory.create(mFakeFeatureFlags).getKeyguardInteractor(),
mKeyguardClockInteractor,
mFakeFeatureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 5e2423a..ef7798e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -447,10 +447,6 @@
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles_default, "spec1,spec1");
List<String> specs = QSTileHost.loadTileSpecs(mContext, "default");
-
- // Remove spurious tiles, like dbg:mem
- specs.removeIf(spec -> !"spec1".equals(spec));
- assertEquals(1, specs.size());
}
@Test
@@ -458,10 +454,6 @@
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles_default, "spec1");
List<String> specs = QSTileHost.loadTileSpecs(mContext, "default,spec1");
-
- // Remove spurious tiles, like dbg:mem
- specs.removeIf(spec -> !"spec1".equals(spec));
- assertEquals(1, specs.size());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
index 067218a..5201e5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
@@ -50,7 +50,6 @@
import com.android.systemui.qs.tiles.ScreenRecordTile
import com.android.systemui.qs.tiles.UiModeNightTile
import com.android.systemui.qs.tiles.WorkModeTile
-import com.android.systemui.util.leak.GarbageMonitor
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -117,7 +116,6 @@
@Mock private lateinit var dataSaverTile: DataSaverTile
@Mock private lateinit var nightDisplayTile: NightDisplayTile
@Mock private lateinit var nfcTile: NfcTile
- @Mock private lateinit var memoryTile: GarbageMonitor.MemoryTile
@Mock private lateinit var darkModeTile: UiModeNightTile
@Mock private lateinit var screenRecordTile: ScreenRecordTile
@Mock private lateinit var reduceBrightColorsTile: ReduceBrightColorsTile
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
index d4e8d37..72fc65b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
@@ -10,8 +10,6 @@
import androidx.constraintlayout.widget.Guideline
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
@@ -39,7 +37,6 @@
lateinit var detectionNoticeView: ViewGroup
lateinit var container: FrameLayout
- var featureFlags = FakeFeatureFlags()
lateinit var screenshotView: ViewGroup
val userHandle = UserHandle.of(5)
@@ -55,7 +52,6 @@
MessageContainerController(
workProfileMessageController,
screenshotDetectionController,
- featureFlags
)
screenshotView = ConstraintLayout(mContext)
workProfileData = WorkProfileMessageController.WorkProfileFirstRunData(appName, icon)
@@ -105,8 +101,6 @@
@Test
fun testOnScreenshotTakenScreenshotData_nothingToShow() {
- featureFlags.set(Flags.SCREENSHOT_DETECTION, true)
-
messageContainer.onScreenshotTaken(screenshotData)
verify(workProfileMessageController, never()).populateView(any(), any(), any())
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 1cc611c..14751c2 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
@@ -43,7 +43,6 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.animation.AnimatorTestRule;
-import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.log.LogBuffer;
@@ -57,9 +56,7 @@
import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore;
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
@@ -70,7 +67,6 @@
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewBinder;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewModel;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.ui.SystemBarUtilsState;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
import com.android.systemui.util.CarrierConfigTracker;
@@ -702,7 +698,7 @@
mKeyguardStateController,
mShadeViewController,
mStatusBarStateController,
- mock(StatusBarIconViewBindingFailureTracker.class),
+ mock(NotificationIconContainerStatusBarViewBinder.class),
mCommandQueue,
mCarrierConfigTracker,
new CollapsedStatusBarFragmentLogger(
@@ -715,10 +711,6 @@
mDumpManager,
mStatusBarWindowStateController,
mKeyguardUpdateMonitor,
- mock(NotificationIconContainerStatusBarViewModel.class),
- mock(ConfigurationState.class),
- mock(SystemBarUtilsState.class),
- mock(StatusBarNotificationIconViewStore.class),
mock(DemoModeController.class));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index bf851eb..6714c94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -1115,6 +1115,7 @@
broadcastDispatcher = fakeBroadcastDispatcher,
keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = utils.testDispatcher,
+ mainDispatcher = utils.testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
index d1870b1..21d4549 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -258,6 +258,7 @@
broadcastDispatcher = fakeBroadcastDispatcher,
keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor = guestUserInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index b7b24f6..d0804be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -170,6 +170,7 @@
broadcastDispatcher = fakeBroadcastDispatcher,
keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor = guestUserInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/GarbageMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/leak/GarbageMonitorTest.java
deleted file mode 100644
index a2b016f..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/util/leak/GarbageMonitorTest.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.util.leak;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.concurrency.MessageRouterImpl;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class GarbageMonitorTest extends SysuiTestCase {
-
- @Mock private LeakReporter mLeakReporter;
- @Mock private TrackedGarbage mTrackedGarbage;
- @Mock private DumpManager mDumpManager;
- private GarbageMonitor mGarbageMonitor;
- private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mGarbageMonitor =
- new GarbageMonitor(
- mContext,
- mFakeExecutor,
- new MessageRouterImpl(mFakeExecutor),
- new LeakDetector(null, mTrackedGarbage, null, mDumpManager),
- mLeakReporter,
- mDumpManager);
- }
-
- @Test
- public void testALittleGarbage_doesntDump() {
- when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE);
-
- mGarbageMonitor.reinspectGarbageAfterGc();
-
- verify(mLeakReporter, never()).dumpLeak(anyInt());
- }
-
- @Test
- public void testTransientGarbage_doesntDump() {
- when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
-
- // Start the leak monitor. Nothing gets reported immediately.
- mGarbageMonitor.startLeakMonitor();
- mFakeExecutor.runAllReady();
- verify(mLeakReporter, never()).dumpLeak(anyInt());
-
- // Garbage gets reset to 0 before the leak reporte actually gets called.
- when(mTrackedGarbage.countOldGarbage()).thenReturn(0);
- mFakeExecutor.advanceClockToLast();
- mFakeExecutor.runAllReady();
-
- // Therefore nothing gets dumped.
- verify(mLeakReporter, never()).dumpLeak(anyInt());
- }
-
- @Test
- public void testLotsOfPersistentGarbage_dumps() {
- when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
-
- mGarbageMonitor.reinspectGarbageAfterGc();
-
- verify(mLeakReporter).dumpLeak(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
- }
-
- @Test
- public void testLotsOfPersistentGarbage_dumpsAfterAtime() {
- when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
-
- // Start the leak monitor. Nothing gets reported immediately.
- mGarbageMonitor.startLeakMonitor();
- mFakeExecutor.runAllReady();
- verify(mLeakReporter, never()).dumpLeak(anyInt());
-
- mFakeExecutor.advanceClockToLast();
- mFakeExecutor.runAllReady();
-
- verify(mLeakReporter).dumpLeak(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ShelfNotificationIconViewStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt
similarity index 64%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ShelfNotificationIconViewStoreKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt
index f7f16a4..67fecb4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ShelfNotificationIconViewStoreKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt
@@ -16,9 +16,22 @@
package com.android.systemui.statusbar.notification.icon.ui.viewbinder
+import com.android.systemui.common.ui.configurationState
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.notificationIconContainerShelfViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.notifCollection
+import com.android.systemui.statusbar.ui.systemBarUtilsState
+
+val Kosmos.notificationIconContainerShelfViewBinder by Fixture {
+ NotificationIconContainerShelfViewBinder(
+ notificationIconContainerShelfViewModel,
+ configurationState,
+ systemBarUtilsState,
+ statusBarIconViewBindingFailureTracker,
+ shelfNotificationIconViewStore,
+ )
+}
val Kosmos.shelfNotificationIconViewStore by Fixture {
ShelfNotificationIconViewStore(notifCollection = notifCollection)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelKosmos.kt
index 988172c..b906b60 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelKosmos.kt
@@ -18,7 +18,6 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.notificationIconContainerShelfViewModel
import com.android.systemui.statusbar.notification.row.ui.viewmodel.activatableNotificationViewModel
import com.android.systemui.statusbar.notification.shelf.domain.interactor.notificationShelfInteractor
@@ -26,6 +25,5 @@
NotificationShelfViewModel(
interactor = notificationShelfInteractor,
activatableViewModel = activatableNotificationViewModel,
- icons = notificationIconContainerShelfViewModel,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
index ca5b401..04716b9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
@@ -22,11 +22,9 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.shelfNotificationIconViewStore
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.statusBarIconViewBindingFailureTracker
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.notificationIconContainerShelfViewBinder
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel
import com.android.systemui.statusbar.phone.notificationIconAreaController
-import com.android.systemui.statusbar.ui.systemBarUtilsState
val Kosmos.notificationListViewBinder by Fixture {
NotificationListViewBinder(
@@ -35,9 +33,7 @@
configuration = configurationState,
falsingManager = falsingManager,
iconAreaController = notificationIconAreaController,
- iconViewBindingFailureTracker = statusBarIconViewBindingFailureTracker,
metricsLogger = metricsLogger,
- shelfIconViewStore = shelfNotificationIconViewStore,
- systemBarUtilsState = systemBarUtilsState,
+ nicBinder = notificationIconContainerShelfViewBinder,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
index 42c77aa..4e2dc7a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
@@ -47,6 +47,7 @@
broadcastDispatcher = broadcastDispatcher,
keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor = guestUserInteractor,
diff --git a/services/tests/media/OWNERS b/services/tests/media/OWNERS
new file mode 100644
index 0000000..160767a6
--- /dev/null
+++ b/services/tests/media/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 137631
+include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
diff --git a/services/tests/media/mediarouterservicetest/Android.bp b/services/tests/media/mediarouterservicetest/Android.bp
new file mode 100644
index 0000000..aed3af6
--- /dev/null
+++ b/services/tests/media/mediarouterservicetest/Android.bp
@@ -0,0 +1,39 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "MediaRouterServiceTests",
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.rules",
+ "androidx.test.runner",
+ "compatibility-device-util-axt",
+ "junit",
+ "platform-test-annotations",
+ "services.core",
+ "truth",
+ ],
+
+ platform_apis: true,
+
+ test_suites: [
+ // "device-tests",
+ "general-tests",
+ ],
+
+ certificate: "platform",
+ dxflags: ["--multi-dex"],
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/services/tests/media/mediarouterservicetest/AndroidManifest.xml b/services/tests/media/mediarouterservicetest/AndroidManifest.xml
new file mode 100644
index 0000000..fe65f86
--- /dev/null
+++ b/services/tests/media/mediarouterservicetest/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.media.tests">
+
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
+
+ <application android:testOnly="true" android:debuggable="true">
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.media.tests"
+ android:label="Frameworks Services Tests"/>
+</manifest>
diff --git a/services/tests/media/mediarouterservicetest/AndroidTest.xml b/services/tests/media/mediarouterservicetest/AndroidTest.xml
new file mode 100644
index 0000000..b065681
--- /dev/null
+++ b/services/tests/media/mediarouterservicetest/AndroidTest.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.
+-->
+<configuration description="Runs MediaRouter Service tests.">
+ <option name="test-tag" value="MediaRouterServiceTests" />
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="MediaRouterServiceTests.apk"/>
+ <option name="install-arg" value="-t" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.server.media.tests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
new file mode 100644
index 0000000..6f9b6fa
--- /dev/null
+++ b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
@@ -0,0 +1,341 @@
+/*
+ * 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.media;
+
+import static com.android.server.media.AudioRoutingUtils.ATTRIBUTES_MEDIA;
+import static com.android.server.media.AudioRoutingUtils.getMediaAudioProductStrategy;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioDevicePort;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.media.MediaRoute2Info;
+import android.media.audiopolicy.AudioProductStrategy;
+import android.os.Looper;
+import android.os.UserHandle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@RunWith(JUnit4.class)
+public class AudioPoliciesDeviceRouteControllerTest {
+
+ private static final String FAKE_ROUTE_NAME = "fake name";
+ private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER =
+ createAudioDeviceInfo(
+ AudioSystem.DEVICE_OUT_SPEAKER, "name_builtin", /* address= */ null);
+ private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET =
+ createAudioDeviceInfo(
+ AudioSystem.DEVICE_OUT_WIRED_HEADSET, "name_wired_hs", /* address= */ null);
+ private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP =
+ createAudioDeviceInfo(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "name_a2dp", /* address= */ "12:34:45");
+
+ private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_BUILTIN_EARPIECE =
+ createAudioDeviceInfo(
+ AudioSystem.DEVICE_OUT_EARPIECE, /* name= */ null, /* address= */ null);
+
+ private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_NO_NAME =
+ createAudioDeviceInfo(
+ AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET,
+ /* name= */ null,
+ /* address= */ null);
+
+ private AudioDeviceInfo mSelectedAudioDeviceInfo;
+ private Set<AudioDeviceInfo> mAvailableAudioDeviceInfos;
+ @Mock private AudioManager mMockAudioManager;
+ @Mock private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
+ private AudioPoliciesDeviceRouteController mControllerUnderTest;
+ private AudioDeviceCallback mAudioDeviceCallback;
+ private AudioProductStrategy mMediaAudioProductStrategy;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ Resources mockResources = Mockito.mock(Resources.class);
+ when(mockResources.getText(anyInt())).thenReturn(FAKE_ROUTE_NAME);
+ Context realContext = InstrumentationRegistry.getInstrumentation().getContext();
+ Context mockContext = Mockito.mock(Context.class);
+ when(mockContext.getResources()).thenReturn(mockResources);
+ // The bluetooth stack needs the application info, but we cannot use a spy because the
+ // concrete class is package private, so we just return the application info through the
+ // mock.
+ when(mockContext.getApplicationInfo()).thenReturn(realContext.getApplicationInfo());
+
+ // Setup the initial state so that the route controller is created in a sensible state.
+ mSelectedAudioDeviceInfo = FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER;
+ mAvailableAudioDeviceInfos = Set.of(FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER);
+ updateMockAudioManagerState();
+ mMediaAudioProductStrategy = getMediaAudioProductStrategy();
+
+ BluetoothAdapter btAdapter =
+ realContext.getSystemService(BluetoothManager.class).getAdapter();
+ mControllerUnderTest =
+ new AudioPoliciesDeviceRouteController(
+ mockContext,
+ mMockAudioManager,
+ Looper.getMainLooper(),
+ mMediaAudioProductStrategy,
+ btAdapter,
+ mOnDeviceRouteChangedListener);
+ mControllerUnderTest.start(UserHandle.CURRENT_OR_SELF);
+
+ ArgumentCaptor<AudioDeviceCallback> deviceCallbackCaptor =
+ ArgumentCaptor.forClass(AudioDeviceCallback.class);
+ verify(mMockAudioManager)
+ .registerAudioDeviceCallback(deviceCallbackCaptor.capture(), any());
+ mAudioDeviceCallback = deviceCallbackCaptor.getValue();
+
+ // We clear any invocations during setup.
+ clearInvocations(mOnDeviceRouteChangedListener);
+ }
+
+ @Test
+ public void getSelectedRoute_afterDevicesConnect_returnsRightSelectedRoute() {
+ assertThat(mControllerUnderTest.getSelectedRoute().getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP);
+ verify(mOnDeviceRouteChangedListener).onDeviceRouteChanged();
+ assertThat(mControllerUnderTest.getSelectedRoute().getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
+
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ null, // Selected device doesn't change.
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET);
+ assertThat(mControllerUnderTest.getSelectedRoute().getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
+ }
+
+ @Test
+ public void getSelectedRoute_afterDeviceRemovals_returnsExpectedRoutes() {
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP,
+ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET);
+ verify(mOnDeviceRouteChangedListener).onDeviceRouteChanged();
+
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP);
+ verify(mOnDeviceRouteChangedListener, times(2)).onDeviceRouteChanged();
+ assertThat(mControllerUnderTest.getSelectedRoute().getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
+
+ removeAvailableAudioDeviceInfos(
+ /* newSelectedDevice= */ null,
+ /* devicesToRemove...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET);
+ assertThat(mControllerUnderTest.getSelectedRoute().getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
+
+ removeAvailableAudioDeviceInfos(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER,
+ /* devicesToRemove...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET);
+ assertThat(mControllerUnderTest.getSelectedRoute().getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+ }
+
+ @Test
+ public void onAudioDevicesAdded_clearsAudioRoutingPoliciesCorrectly() {
+ clearInvocations(mMockAudioManager);
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ null, // Selected device doesn't change.
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_BUILTIN_EARPIECE);
+ verifyNoMoreInteractions(mMockAudioManager);
+
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP);
+ verify(mMockAudioManager).removePreferredDeviceForStrategy(mMediaAudioProductStrategy);
+ }
+
+ @Test
+ public void getAvailableDevices_ignoresInvalidMediaOutputs() {
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ null, // Selected device doesn't change.
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_BUILTIN_EARPIECE);
+ verifyNoMoreInteractions(mOnDeviceRouteChangedListener);
+ assertThat(
+ mControllerUnderTest.getAvailableRoutes().stream()
+ .map(MediaRoute2Info::getType)
+ .toList())
+ .containsExactly(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+ assertThat(mControllerUnderTest.getSelectedRoute().getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+ }
+
+ @Test
+ public void transferTo_setsTheExpectedRoutingPolicy() {
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP,
+ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET);
+ MediaRoute2Info builtInSpeakerRoute =
+ getAvailableRouteWithType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+ mControllerUnderTest.transferTo(builtInSpeakerRoute.getId());
+ verify(mMockAudioManager)
+ .setPreferredDeviceForStrategy(
+ mMediaAudioProductStrategy,
+ createAudioDeviceAttribute(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER));
+
+ MediaRoute2Info wiredHeadsetRoute =
+ getAvailableRouteWithType(MediaRoute2Info.TYPE_WIRED_HEADSET);
+ mControllerUnderTest.transferTo(wiredHeadsetRoute.getId());
+ verify(mMockAudioManager)
+ .setPreferredDeviceForStrategy(
+ mMediaAudioProductStrategy,
+ createAudioDeviceAttribute(AudioDeviceInfo.TYPE_WIRED_HEADSET));
+ }
+
+ @Test
+ public void updateVolume_propagatesCorrectlyToRouteInfo() {
+ when(mMockAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)).thenReturn(2);
+ when(mMockAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)).thenReturn(3);
+ when(mMockAudioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC)).thenReturn(1);
+ when(mMockAudioManager.isVolumeFixed()).thenReturn(false);
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET);
+
+ MediaRoute2Info selectedRoute = mControllerUnderTest.getSelectedRoute();
+ assertThat(selectedRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADSET);
+ assertThat(selectedRoute.getVolume()).isEqualTo(2);
+ assertThat(selectedRoute.getVolumeMax()).isEqualTo(3);
+ assertThat(selectedRoute.getVolumeHandling())
+ .isEqualTo(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE);
+
+ MediaRoute2Info onlyTransferrableRoute =
+ mControllerUnderTest.getAvailableRoutes().stream()
+ .filter(it -> !it.equals(selectedRoute))
+ .findAny()
+ .orElseThrow();
+ assertThat(onlyTransferrableRoute.getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+ assertThat(onlyTransferrableRoute.getVolume()).isEqualTo(0);
+ assertThat(onlyTransferrableRoute.getVolumeMax()).isEqualTo(0);
+ assertThat(onlyTransferrableRoute.getVolume()).isEqualTo(0);
+ assertThat(onlyTransferrableRoute.getVolumeHandling())
+ .isEqualTo(MediaRoute2Info.PLAYBACK_VOLUME_FIXED);
+
+ when(mMockAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)).thenReturn(0);
+ when(mMockAudioManager.isVolumeFixed()).thenReturn(true);
+ mControllerUnderTest.updateVolume(0);
+ MediaRoute2Info newSelectedRoute = mControllerUnderTest.getSelectedRoute();
+ assertThat(newSelectedRoute.getVolume()).isEqualTo(0);
+ assertThat(newSelectedRoute.getVolumeHandling())
+ .isEqualTo(MediaRoute2Info.PLAYBACK_VOLUME_FIXED);
+ }
+
+ @Test
+ public void getAvailableRoutes_whenNoProductNameIsProvided_usesTypeToPopulateName() {
+ assertThat(mControllerUnderTest.getSelectedRoute().getName().toString())
+ .isEqualTo(FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER.getProductName().toString());
+
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_NO_NAME,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_NO_NAME);
+
+ MediaRoute2Info selectedRoute = mControllerUnderTest.getSelectedRoute();
+ assertThat(selectedRoute.getName().toString()).isEqualTo(FAKE_ROUTE_NAME);
+ }
+
+ // Internal methods.
+
+ @NonNull
+ private MediaRoute2Info getAvailableRouteWithType(int type) {
+ return mControllerUnderTest.getAvailableRoutes().stream()
+ .filter(it -> it.getType() == type)
+ .findFirst()
+ .orElseThrow();
+ }
+
+ private void addAvailableAudioDeviceInfo(
+ @Nullable AudioDeviceInfo newSelectedDevice, AudioDeviceInfo... newAvailableDevices) {
+ Set<AudioDeviceInfo> newAvailableDeviceInfos = new HashSet<>(mAvailableAudioDeviceInfos);
+ newAvailableDeviceInfos.addAll(List.of(newAvailableDevices));
+ mAvailableAudioDeviceInfos = newAvailableDeviceInfos;
+ if (newSelectedDevice != null) {
+ mSelectedAudioDeviceInfo = newSelectedDevice;
+ }
+ updateMockAudioManagerState();
+ mAudioDeviceCallback.onAudioDevicesAdded(newAvailableDevices);
+ }
+
+ private void removeAvailableAudioDeviceInfos(
+ @Nullable AudioDeviceInfo newSelectedDevice, AudioDeviceInfo... devicesToRemove) {
+ Set<AudioDeviceInfo> newAvailableDeviceInfos = new HashSet<>(mAvailableAudioDeviceInfos);
+ List.of(devicesToRemove).forEach(newAvailableDeviceInfos::remove);
+ mAvailableAudioDeviceInfos = newAvailableDeviceInfos;
+ if (newSelectedDevice != null) {
+ mSelectedAudioDeviceInfo = newSelectedDevice;
+ }
+ updateMockAudioManagerState();
+ mAudioDeviceCallback.onAudioDevicesRemoved(devicesToRemove);
+ }
+
+ private void updateMockAudioManagerState() {
+ when(mMockAudioManager.getDevicesForAttributes(ATTRIBUTES_MEDIA))
+ .thenReturn(
+ List.of(createAudioDeviceAttribute(mSelectedAudioDeviceInfo.getType())));
+ when(mMockAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))
+ .thenReturn(mAvailableAudioDeviceInfos.toArray(new AudioDeviceInfo[0]));
+ }
+
+ private static AudioDeviceAttributes createAudioDeviceAttribute(int type) {
+ // Address is unused.
+ return new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT, type, /* address= */ "");
+ }
+
+ private static AudioDeviceInfo createAudioDeviceInfo(
+ int type, @NonNull String name, @NonNull String address) {
+ return new AudioDeviceInfo(AudioDevicePort.createForTesting(type, name, address));
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 650c473..d24500d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -28,6 +28,8 @@
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.mockitoSession;
+import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
+import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS;
import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_APPLIED_CONSTRAINTS;
@@ -212,6 +214,7 @@
JobStatus js = JobStatus.createFromJobInfo(
jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, "FCTest", testTag);
js.enqueueTime = FROZEN_TIME;
+ js.setStandbyBucket(ACTIVE_INDEX);
if (js.hasFlexibilityConstraint()) {
js.setNumAppliedFlexibleConstraints(Integer.bitCount(
mFlexibilityController.getRelevantAppliedConstraintsLocked(js)));
@@ -847,14 +850,75 @@
}
@Test
+ public void testAllowlistedAppBypass() {
+ JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
+ JobStatus jsDefault = createJobStatus("testAllowlistedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT));
+ JobStatus jsLow = createJobStatus("testAllowlistedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW));
+ JobStatus jsMin = createJobStatus("testAllowlistedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN));
+ jsHigh.setStandbyBucket(EXEMPTED_INDEX);
+ jsDefault.setStandbyBucket(EXEMPTED_INDEX);
+ jsLow.setStandbyBucket(EXEMPTED_INDEX);
+ jsMin.setStandbyBucket(EXEMPTED_INDEX);
+
+ synchronized (mFlexibilityController.mLock) {
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
+ }
+ }
+
+ @Test
+ public void testForegroundAppBypass() {
+ JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
+ JobStatus jsDefault = createJobStatus("testAllowlistedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT));
+ JobStatus jsLow = createJobStatus("testAllowlistedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW));
+ JobStatus jsMin = createJobStatus("testAllowlistedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN));
+
+ when(mJobSchedulerService.getUidBias(mSourceUid)).thenReturn(JobInfo.BIAS_DEFAULT);
+ synchronized (mFlexibilityController.mLock) {
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
+ }
+
+ when(mJobSchedulerService.getUidBias(mSourceUid))
+ .thenReturn(JobInfo.BIAS_BOUND_FOREGROUND_SERVICE);
+ synchronized (mFlexibilityController.mLock) {
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
+ }
+
+ when(mJobSchedulerService.getUidBias(mSourceUid))
+ .thenReturn(JobInfo.BIAS_FOREGROUND_SERVICE);
+ synchronized (mFlexibilityController.mLock) {
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
+ }
+ }
+
+ @Test
public void testTopAppBypass() {
- JobInfo.Builder jb = createJob(0);
+ JobInfo.Builder jb = createJob(0).setPriority(JobInfo.PRIORITY_MIN);
JobStatus js = createJobStatus("testTopAppBypass", jb);
mJobStore.add(js);
// Needed because if before and after Uid bias is the same, nothing happens.
when(mJobSchedulerService.getUidBias(mSourceUid))
- .thenReturn(JobInfo.BIAS_FOREGROUND_SERVICE);
+ .thenReturn(JobInfo.BIAS_DEFAULT);
synchronized (mFlexibilityController.mLock) {
mFlexibilityController.maybeStartTrackingJobLocked(js, null);
@@ -865,7 +929,7 @@
assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
assertTrue(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));
- setUidBias(mSourceUid, JobInfo.BIAS_FOREGROUND_SERVICE);
+ setUidBias(mSourceUid, JobInfo.BIAS_SYNC_INITIALIZATION);
assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
assertFalse(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));