summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java24
-rw-r--r--core/api/current.txt8
-rw-r--r--core/java/android/app/ActivityManagerInternal.java8
-rw-r--r--core/java/android/view/SurfaceControlViewHost.java3
-rw-r--r--core/java/android/view/SurfaceView.java2
-rw-r--r--core/java/android/view/ViewRootImpl.java10
-rw-r--r--core/java/android/window/SurfaceSyncGroup.java330
-rw-r--r--core/java/android/window/SurfaceSyncGroup.md2
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java41
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java19
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt38
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt14
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt50
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt13
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.kt53
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt2
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt4
-rw-r--r--packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml2
-rw-r--r--packages/SystemUI/res/layout/global_actions_grid_lite.xml4
-rw-r--r--packages/SystemUI/res/layout/keyguard_qs_user_switch.xml4
-rw-r--r--packages/SystemUI/res/layout/media_recommendation_view.xml65
-rw-r--r--packages/SystemUI/res/layout/media_recommendations.xml75
-rw-r--r--packages/SystemUI/res/layout/media_session_view.xml4
-rw-r--r--packages/SystemUI/res/layout/ongoing_call_chip.xml4
-rw-r--r--packages/SystemUI/res/values/dimens.xml5
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/res/values/styles.xml17
-rw-r--r--packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml64
-rw-r--r--packages/SystemUI/res/xml/media_recommendations_view_expanded.xml71
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableConstraintLayout.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt94
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java143
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/MediaProjectionDevicePolicyModule.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolver.kt125
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt44
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt49
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt703
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt3
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java19
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java62
-rw-r--r--services/core/java/com/android/server/audio/AudioSystemAdapter.java62
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java2
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java14
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController2.java14
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerControllerInterface.java6
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java8
-rw-r--r--services/core/java/com/android/server/wm/SurfaceSyncGroupController.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java69
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java56
-rw-r--r--services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java15
-rw-r--r--services/tests/wmtests/AndroidManifest.xml3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java54
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java (renamed from services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupContinuousTest.java)34
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncValidatorTestCase.java (renamed from services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupValidatorTestCase.java)8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/scvh/EmbeddedSCVHService.java127
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindow.aidl27
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindowCallback.aidl23
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/scvh/SyncValidatorSCVHTestCase.java241
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java17
-rw-r--r--tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java4
-rw-r--r--tests/SurfaceViewSyncTest/src/com/android/test/SurfaceViewSyncActivity.java4
81 files changed, 2463 insertions, 877 deletions
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 299cb6cddbdf..e8fcdd2979de 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1584,6 +1584,12 @@ public class JobSchedulerService extends com.android.server.SystemService
return reason;
}
+ @VisibleForTesting
+ @JobScheduler.PendingJobReason
+ int getPendingJobReason(JobStatus job) {
+ return getPendingJobReason(job.getUid(), job.getNamespace(), job.getJobId());
+ }
+
@JobScheduler.PendingJobReason
@GuardedBy("mLock")
private int getPendingJobReasonLocked(int uid, String namespace, int jobId) {
@@ -1694,7 +1700,8 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
- private void stopUserVisibleJobsInternal(@NonNull String packageName, int userId) {
+ @VisibleForTesting
+ void stopUserVisibleJobsInternal(@NonNull String packageName, int userId) {
final int packageUid = mLocalPM.getPackageUid(packageName, 0, userId);
if (packageUid < 0) {
Slog.wtf(TAG, "Asked to stop jobs of an unknown package");
@@ -1716,6 +1723,21 @@ public class JobSchedulerService extends com.android.server.SystemService
// to stop only that work, B's jobs would be demoted as well.
// TODO(255768978): make it possible to demote only the relevant subset of jobs
job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
+
+ // The app process will be killed soon. There's no point keeping its jobs in
+ // the pending queue to try and start them.
+ if (mPendingJobQueue.remove(job)) {
+ synchronized (mPendingJobReasonCache) {
+ SparseIntArray jobIdToReason = mPendingJobReasonCache.get(
+ job.getUid(), job.getNamespace());
+ if (jobIdToReason == null) {
+ jobIdToReason = new SparseIntArray();
+ mPendingJobReasonCache.add(job.getUid(), job.getNamespace(),
+ jobIdToReason);
+ }
+ jobIdToReason.put(job.getJobId(), JobScheduler.PENDING_JOB_REASON_USER);
+ }
+ }
}
}
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 3c2d22f9069b..deb4b7ce0618 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -60276,6 +60276,14 @@ package android.window {
method @UiThread public void remove();
}
+ public class SurfaceSyncGroup {
+ ctor public SurfaceSyncGroup(@NonNull String);
+ method @UiThread public boolean add(@Nullable android.view.AttachedSurfaceControl, @Nullable Runnable);
+ method public boolean add(@NonNull android.view.SurfaceControlViewHost.SurfacePackage, @Nullable Runnable);
+ method public void addTransaction(@NonNull android.view.SurfaceControl.Transaction);
+ method public void markSyncReady();
+ }
+
}
package javax.microedition.khronos.egl {
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index ce29937c7cad..0fa1a378ed39 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -955,4 +955,12 @@ public abstract class ActivityManagerInternal {
* @hide
*/
public abstract void stopForegroundServiceDelegate(@NonNull ServiceConnection connection);
+
+ /**
+ * Called by PowerManager. Return whether a given procstate is allowed to hold
+ * wake locks in deep doze. Because it's called with the power manager lock held, we can't
+ * hold AM locks in it.
+ * @hide
+ */
+ public abstract boolean canHoldWakeLocksInDeepDoze(int uid, int procstate);
}
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 490091bf2352..f7bdd094ff2c 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -97,7 +97,8 @@ public class SurfaceControlViewHost {
public ISurfaceSyncGroup getSurfaceSyncGroup() {
CompletableFuture<ISurfaceSyncGroup> surfaceSyncGroup = new CompletableFuture<>();
mViewRoot.mHandler.post(
- () -> surfaceSyncGroup.complete(mViewRoot.getOrCreateSurfaceSyncGroup()));
+ () -> surfaceSyncGroup.complete(
+ mViewRoot.getOrCreateSurfaceSyncGroup().mISurfaceSyncGroup));
try {
return surfaceSyncGroup.get(1, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index e559a71757c8..1eb87c9e039e 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1091,7 +1091,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
t = syncBufferTransactionCallback.waitForTransaction();
}
- surfaceSyncGroup.addTransactionToSync(t);
+ surfaceSyncGroup.addTransaction(t);
surfaceSyncGroup.markSyncReady();
onDrawFinished();
});
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 84ed845d62f8..9119ea4bab94 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3768,7 +3768,7 @@ public final class ViewRootImpl implements ViewParent,
Log.d(mTag, "Setup new sync=" + mWmsRequestSyncGroup.getName());
}
- mWmsRequestSyncGroup.addToSync(this);
+ mWmsRequestSyncGroup.add(this, null /* runnable */);
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
@@ -11354,7 +11354,7 @@ public final class ViewRootImpl implements ViewParent,
// pendingDrawFinished.
if ((syncResult
& (SYNC_LOST_SURFACE_REWARD_IF_FOUND | SYNC_CONTEXT_IS_STOPPED)) != 0) {
- surfaceSyncGroup.addTransactionToSync(
+ surfaceSyncGroup.addTransaction(
mBlastBufferQueue.gatherPendingTransactions(frame));
surfaceSyncGroup.markSyncReady();
return null;
@@ -11368,7 +11368,7 @@ public final class ViewRootImpl implements ViewParent,
mBlastBufferQueue.syncNextTransaction(new Consumer<Transaction>() {
@Override
public void accept(Transaction transaction) {
- surfaceSyncGroup.addTransactionToSync(transaction);
+ surfaceSyncGroup.addTransaction(transaction);
surfaceSyncGroup.markSyncReady();
}
});
@@ -11391,7 +11391,7 @@ public final class ViewRootImpl implements ViewParent,
// since the frame didn't draw on this vsync. It's possible the frame will
// draw later, but it's better to not be sync than to block on a frame that
// may never come.
- surfaceSyncGroup.addTransactionToSync(
+ surfaceSyncGroup.addTransaction(
mBlastBufferQueue.gatherPendingTransactions(frame));
surfaceSyncGroup.markSyncReady();
return;
@@ -11481,7 +11481,7 @@ public final class ViewRootImpl implements ViewParent,
if (mActiveSurfaceSyncGroup == null) {
return;
}
- mActiveSurfaceSyncGroup.addToSync(syncable, false /* parentSyncGroupMerge */);
+ mActiveSurfaceSyncGroup.add(syncable, null /* Runnable */);
}
@Override
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index 12cd3408bf87..2e55041d2e8c 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -42,14 +42,18 @@ import java.util.function.Consumer;
import java.util.function.Supplier;
/**
- * Used to organize syncs for surfaces.
+ * A way for data to be gathered so multiple surfaces can be synced. This is intended to be
+ * used with AttachedSurfaceControl, SurfaceView, and SurfaceControlViewHost. This allows different
+ * parts of the system to synchronize different surfaces themselves without having to manage timing
+ * of different rendering threads.
+ * This will also allow synchronization of surfaces across multiple processes. The caller can add
+ * SurfaceControlViewHosts from another process to the SurfaceSyncGroup in a different process
+ * and this clas will ensure all the surfaces are ready before applying everything together.
* </p>
- * See SurfaceSyncGroup.md
+ * see the <a href="https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/window/SurfaceSyncGroup.md">SurfaceSyncGroup documentation</a>
* </p>
- *
- * @hide
*/
-public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
+public class SurfaceSyncGroup {
private static final String TAG = "SurfaceSyncGroup";
private static final boolean DEBUG = false;
@@ -93,6 +97,11 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
private ISurfaceSyncGroupCompletedListener mSurfaceSyncGroupCompletedListener;
/**
+ * @hide
+ */
+ public final ISurfaceSyncGroup mISurfaceSyncGroup = new ISurfaceSyncGroupImpl();
+
+ /**
* Token to identify this SurfaceSyncGroup. This is used to register the SurfaceSyncGroup in
* WindowManager. This token is also sent to other processes' SurfaceSyncGroup that want to be
* included in this SurfaceSyncGroup.
@@ -104,8 +113,8 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
}
private static SurfaceSyncGroup getSurfaceSyncGroup(ISurfaceSyncGroup iSurfaceSyncGroup) {
- if (iSurfaceSyncGroup instanceof SurfaceSyncGroup) {
- return (SurfaceSyncGroup) iSurfaceSyncGroup;
+ if (iSurfaceSyncGroup instanceof ISurfaceSyncGroupImpl) {
+ return ((ISurfaceSyncGroupImpl) iSurfaceSyncGroup).getSurfaceSyncGroup();
}
return null;
}
@@ -119,8 +128,10 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
/**
* Starts a sync and will automatically apply the final, merged transaction.
+ *
+ * @param name Used for identifying and debugging.
*/
- public SurfaceSyncGroup(String name) {
+ public SurfaceSyncGroup(@NonNull String name) {
this(name, transaction -> {
if (transaction != null) {
if (DEBUG) {
@@ -134,6 +145,7 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
/**
* Creates a sync.
*
+ * @param name Used for identifying and debugging.
* @param transactionReadyConsumer The complete callback that contains the syncId and
* transaction with all the sync data merged. The Transaction
* passed back can be null.
@@ -157,19 +169,23 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
Log.d(TAG, "Sending non null transaction " + transaction + " to callback for "
+ mName);
}
- Trace.instant(Trace.TRACE_TAG_VIEW,
- "Final TransactionCallback with " + transaction + " for " + mName);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.instant(Trace.TRACE_TAG_VIEW,
+ "Final TransactionCallback with " + transaction + " for " + mName);
+ }
transactionReadyConsumer.accept(transaction);
synchronized (mLock) {
// If there's a registered listener with WMS, that means we aren't actually complete
// until WMS notifies us that the parent has completed.
if (mSurfaceSyncGroupCompletedListener == null) {
- invokeSyncCompleteListeners();
+ invokeSyncCompleteCallbacks();
}
}
};
- Trace.instant(Trace.TRACE_TAG_VIEW, "new SurfaceSyncGroup " + mName);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.instant(Trace.TRACE_TAG_VIEW, "new SurfaceSyncGroup " + mName);
+ }
if (DEBUG) {
Log.d(TAG, "setupSync " + mName + " " + Debug.getCallers(2));
@@ -177,7 +193,7 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
}
@GuardedBy("mLock")
- private void invokeSyncCompleteListeners() {
+ private void invokeSyncCompleteCallbacks() {
mSyncCompleteCallbacks.forEach(
executorRunnablePair -> executorRunnablePair.first.execute(
executorRunnablePair.second));
@@ -188,6 +204,7 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
*
* @param executor The Executor to invoke the Runnable on
* @param runnable The Runnable to get called
+ * @hide
*/
public void addSyncCompleteCallback(Executor executor, Runnable runnable) {
synchronized (mLock) {
@@ -196,13 +213,16 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
}
/**
- * Mark the sync set as ready to complete. No more data can be added to the specified
- * syncId.
- * Once the sync set is marked as ready, it will be able to complete once all Syncables in the
- * set have completed their sync
+ * Mark the SurfaceSyncGroup as ready to complete. No more data can be added to this
+ * SurfaceSyncGroup.
+ * <p>
+ * Once the SurfaceSyncGroup is marked as ready, it will be able to complete once all child
+ * SurfaceSyncGroup have completed their sync.
*/
public void markSyncReady() {
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "markSyncReady " + mName);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "markSyncReady " + mName);
+ }
synchronized (mLock) {
if (mHasWMSync) {
try {
@@ -213,28 +233,29 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
mSyncReady = true;
checkIfSyncIsComplete();
}
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
}
/**
- * Add a SurfaceView to a sync set. This is different than
- * {@link #addToSync(AttachedSurfaceControl)} because it requires the caller to notify the start
- * and finish drawing in order to sync.
+ * Add a SurfaceView to a SurfaceSyncGroup. This requires the caller to notify the start
+ * and finish drawing in order to sync since the client owns the rendering of the SurfaceView.
*
* @param surfaceView The SurfaceView to add to the sync.
* @param frameCallbackConsumer The callback that's invoked to allow the caller to notify
- * the
- * Syncer when the SurfaceView has started drawing and
- * finished.
+ * SurfaceSyncGroup when the SurfaceView has started drawing.
* @return true if the SurfaceView was successfully added to the SyncGroup, false otherwise.
+ * @hide
*/
@UiThread
- public boolean addToSync(SurfaceView surfaceView,
+ public boolean add(SurfaceView surfaceView,
Consumer<SurfaceViewFrameCallback> frameCallbackConsumer) {
SurfaceSyncGroup surfaceSyncGroup = new SurfaceSyncGroup(surfaceView.getName());
- if (addToSync(surfaceSyncGroup, false /* parentSyncGroupMerge */)) {
+ if (add(surfaceSyncGroup.mISurfaceSyncGroup, false /* parentSyncGroupMerge */,
+ null /* runnable */)) {
frameCallbackConsumer.accept(() -> surfaceView.syncNextFrame(transaction -> {
- surfaceSyncGroup.addTransactionToSync(transaction);
+ surfaceSyncGroup.addTransaction(transaction);
surfaceSyncGroup.markSyncReady();
}));
return true;
@@ -243,54 +264,53 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
}
/**
- * Add an AttachedSurfaceControl to a sync set.
- *
- * @param viewRoot The viewRoot that will be add to the sync set.
- * @return true if the View was successfully added to the SyncGroup, false otherwise.
- * @see #addToSync(AttachedSurfaceControl, Runnable)
- */
- @UiThread
- public boolean addToSync(@Nullable AttachedSurfaceControl viewRoot) {
- return addToSync(viewRoot, null /* runnable */);
- }
-
- /**
- * Add an AttachedSurfaceControl to a sync set. The AttachedSurfaceControl will pause rendering
- * to ensure the runnable can be invoked and the sync picks up the frame that contains the
- * changes.
+ * Add an AttachedSurfaceControl to the SurfaceSyncGroup. The AttachedSurfaceControl will pause
+ * rendering to ensure the runnable can be invoked and that the sync picks up the frame that
+ * contains the changes.
*
- * @param viewRoot The viewRoot that will be add to the sync set.
- * @param runnable The runnable to be invoked before adding to the sync group.
- * @return true if the View was successfully added to the SyncGroup, false otherwise.
- * @see #addToSync(AttachedSurfaceControl)
+ * @param attachedSurfaceControl The AttachedSurfaceControl that will be add to this
+ * SurfaceSyncGroup.
+ * @param runnable This is run on the same thread that the call was made on, but
+ * after the rendering is paused and before continuing to render
+ * the next frame. This method will not return until the
+ * execution of the runnable completes. This can be used to make
+ * changes to the AttachedSurfaceControl, ensuring that the
+ * changes are included in the sync.
+ * @return true if the AttachedSurfaceControl was successfully added to the SurfaceSyncGroup,
+ * false otherwise.
*/
@UiThread
- public boolean addToSync(@Nullable AttachedSurfaceControl viewRoot,
+ public boolean add(@Nullable AttachedSurfaceControl attachedSurfaceControl,
@Nullable Runnable runnable) {
- if (viewRoot == null) {
+ if (attachedSurfaceControl == null) {
return false;
}
- SurfaceSyncGroup surfaceSyncGroup = viewRoot.getOrCreateSurfaceSyncGroup();
+ SurfaceSyncGroup surfaceSyncGroup = attachedSurfaceControl.getOrCreateSurfaceSyncGroup();
if (surfaceSyncGroup == null) {
return false;
}
- return addToSync(surfaceSyncGroup, false /* parentSyncGroupMerge */, runnable);
+ return add(surfaceSyncGroup, runnable);
}
/**
- * Helper method to add a SurfaceControlViewHost.SurfacePackage to the sync group. This will
+ * Add a SurfaceControlViewHost.SurfacePackage to the SurfaceSyncGroup. This will
* get the SurfaceSyncGroup from the SurfacePackage, which will pause rendering for the
* SurfaceControlViewHost. The runnable will be invoked to allow the host to update the SCVH
* in a synchronized way. Finally, it will add the SCVH to the SurfaceSyncGroup and unpause
* rendering in the SCVH, allowing the changes to get picked up and included in the sync.
*
- * @param surfacePackage The SurfacePackage that should be synced
- * @param runnable The Runnable that's invoked before getting the frame to sync.
- * @return true if the SCVH was successfully added to the current SyncGroup, false
- * otherwise.
+ * @param surfacePackage The SurfacePackage that will be added to this SurfaceSyncGroup.
+ * @param runnable This is run on the same thread that the call was made on, but
+ * after the rendering is paused and before continuing to render
+ * the next frame. This method will not return until the
+ * execution of the runnable completes. This can be used to make
+ * changes to the SurfaceControlViewHost, ensuring that the
+ * changes are included in the sync.
+ * @return true if the SurfaceControlViewHost was successfully added to the current
+ * SurfaceSyncGroup, false otherwise.
*/
- public boolean addToSync(@NonNull SurfaceControlViewHost.SurfacePackage surfacePackage,
+ public boolean add(@NonNull SurfaceControlViewHost.SurfacePackage surfacePackage,
@Nullable Runnable runnable) {
ISurfaceSyncGroup surfaceSyncGroup;
try {
@@ -305,20 +325,31 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
+ "SCVH returned null SurfaceSyncGroup");
return false;
}
- return addToSync(surfaceSyncGroup, false /* parentSyncGroupMerge */, runnable);
+ return add(surfaceSyncGroup, false /* parentSyncGroupMerge */, runnable);
}
- @Override
- public boolean addToSync(ISurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge) {
- return addToSync(surfaceSyncGroup, parentSyncGroupMerge, null);
+ /**
+ * Add a SurfaceSyncGroup to the current SurfaceSyncGroup.
+ *
+ * @param surfaceSyncGroup The SurfaceSyncGroup that will be added to this SurfaceSyncGroup.
+ * @param runnable This is run on the same thread that the call was made on, This
+ * method will not return until the execution of the runnable
+ * completes. This can be used to make changes to the SurfaceSyncGroup,
+ * ensuring that the changes are included in the sync.
+ * @return true if the requested SurfaceSyncGroup was successfully added to the
+ * SurfaceSyncGroup, false otherwise.
+ * @hide
+ */
+ public boolean add(@NonNull SurfaceSyncGroup surfaceSyncGroup,
+ @Nullable Runnable runnable) {
+ return add(surfaceSyncGroup.mISurfaceSyncGroup, false /* parentSyncGroupMerge */,
+ runnable);
}
/**
- * Add a {@link SurfaceSyncGroup} to a sync set. The sync set will wait for all
- * SyncableSurfaces to complete before notifying.
+ * Add a {@link ISurfaceSyncGroup} to a SurfaceSyncGroup.
*
- * @param surfaceSyncGroup A SyncableSurface that implements how to handle syncing
- * buffers.
+ * @param surfaceSyncGroup An ISyncableSurface that will be added to this SurfaceSyncGroup.
* @param parentSyncGroupMerge true if the ISurfaceSyncGroup is added because its child was
* added to a new SurfaceSyncGroup. That would require the code to
* call newParent.addToSync(oldParent). When this occurs, we need to
@@ -327,15 +358,20 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
* @param runnable The Runnable that's invoked before adding the SurfaceSyncGroup
* @return true if the SyncGroup was successfully added to the current SyncGroup, false
* otherwise.
+ * @hide
*/
- public boolean addToSync(ISurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge,
+ public boolean add(ISurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge,
@Nullable Runnable runnable) {
- Trace.traceBegin(Trace.TRACE_TAG_VIEW,
- "addToSync token=" + mToken.hashCode() + " parent=" + mName);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+ "addToSync token=" + mToken.hashCode() + " parent=" + mName);
+ }
synchronized (mLock) {
if (mSyncReady) {
Log.w(TAG, "Trying to add to sync when already marked as ready " + mName);
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
return false;
}
}
@@ -346,7 +382,9 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
if (isLocalBinder(surfaceSyncGroup.asBinder())) {
boolean didAddLocalSync = addLocalSync(surfaceSyncGroup, parentSyncGroupMerge);
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
return didAddLocalSync;
}
@@ -365,14 +403,16 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
@Override
public void onSurfaceSyncGroupComplete() {
synchronized (mLock) {
- invokeSyncCompleteListeners();
+ invokeSyncCompleteCallbacks();
}
}
};
if (!addSyncToWm(mToken, false /* parentSyncGroupMerge */,
mSurfaceSyncGroupCompletedListener)) {
mSurfaceSyncGroupCompletedListener = null;
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
return false;
}
mHasWMSync = true;
@@ -382,38 +422,35 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
try {
surfaceSyncGroup.onAddedToSyncGroup(mToken, parentSyncGroupMerge);
} catch (RemoteException e) {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
return false;
}
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
return true;
}
- @Override
- public final boolean onAddedToSyncGroup(IBinder parentSyncGroupToken,
- boolean parentSyncGroupMerge) {
- Trace.traceBegin(Trace.TRACE_TAG_VIEW,
- "onAddedToSyncGroup token=" + parentSyncGroupToken.hashCode() + " child=" + mName);
- boolean didAdd = addSyncToWm(parentSyncGroupToken, parentSyncGroupMerge, null);
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- return didAdd;
- }
-
-
/**
- * Add a Transaction to this sync set. This allows the caller to provide other info that
- * should be synced with the transactions.
+ * Add a Transaction to this SurfaceSyncGroup. This allows the caller to provide other info that
+ * should be synced with the other transactions in this SurfaceSyncGroup.
+ *
+ * @param transaction The transaction to add to the SurfaceSyncGroup.
*/
- public void addTransactionToSync(Transaction t) {
+ public void addTransaction(@NonNull Transaction transaction) {
synchronized (mLock) {
- mTransaction.merge(t);
+ mTransaction.merge(transaction);
}
}
/**
* Invoked when the SurfaceSyncGroup has been added to another SurfaceSyncGroup and is ready
* to proceed.
+ *
+ * @hide
*/
public void onSyncReady() {
}
@@ -425,23 +462,31 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
Log.d(TAG, "Attempting to add remote sync to " + mName
+ ". Setting up Sync in WindowManager.");
}
- Trace.traceBegin(Trace.TRACE_TAG_VIEW,
- "addSyncToWm=" + token.hashCode() + " group=" + mName);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+ "addSyncToWm=" + token.hashCode() + " group=" + mName);
+ }
AddToSurfaceSyncGroupResult addToSyncGroupResult = new AddToSurfaceSyncGroupResult();
if (!WindowManagerGlobal.getWindowManagerService().addToSurfaceSyncGroup(token,
parentSyncGroupMerge, surfaceSyncGroupCompletedListener,
addToSyncGroupResult)) {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
return false;
}
setTransactionCallbackFromParent(addToSyncGroupResult.mParentSyncGroup,
addToSyncGroupResult.mTransactionReadyCallback);
} catch (RemoteException e) {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
return false;
}
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
return true;
}
@@ -457,8 +502,10 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
return false;
}
- Trace.traceBegin(Trace.TRACE_TAG_VIEW,
- "addLocalSync=" + childSurfaceSyncGroup.mName + " parent=" + mName);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+ "addLocalSync=" + childSurfaceSyncGroup.mName + " parent=" + mName);
+ }
ITransactionReadyCallback callback =
createTransactionReadyCallback(parentSyncGroupMerge);
@@ -466,8 +513,10 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
return false;
}
- childSurfaceSyncGroup.setTransactionCallbackFromParent(this, callback);
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ childSurfaceSyncGroup.setTransactionCallbackFromParent(mISurfaceSyncGroup, callback);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
return true;
}
@@ -477,9 +526,11 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
Log.d(TAG, "setTransactionCallbackFromParent " + mName);
}
boolean finished = false;
- Trace.traceBegin(Trace.TRACE_TAG_VIEW,
- "setTransactionCallbackFromParent " + mName + " callback="
- + transactionReadyCallback.hashCode());
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+ "setTransactionCallbackFromParent " + mName + " callback="
+ + transactionReadyCallback.hashCode());
+ }
synchronized (mLock) {
if (mFinished) {
finished = true;
@@ -510,9 +561,11 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
Consumer<Transaction> lastCallback = mTransactionReadyConsumer;
mParentSyncGroup = parentSyncGroup;
mTransactionReadyConsumer = (transaction) -> {
- Trace.traceBegin(Trace.TRACE_TAG_VIEW,
- "transactionReadyCallback " + mName + " callback="
- + transactionReadyCallback.hashCode());
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+ "transactionReadyCallback " + mName + " callback="
+ + transactionReadyCallback.hashCode());
+ }
lastCallback.accept(null);
try {
@@ -520,7 +573,9 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
} catch (RemoteException e) {
transaction.apply();
}
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
};
}
}
@@ -535,9 +590,14 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
} else {
onSyncReady();
}
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
}
+ /**
+ * @hide
+ */
public String getName() {
return mName;
}
@@ -552,9 +612,11 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
return;
}
- Trace.instant(Trace.TRACE_TAG_VIEW,
- "checkIfSyncIsComplete " + mName + " mSyncReady=" + mSyncReady + " mPendingSyncs="
- + mPendingSyncs.size());
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.instant(Trace.TRACE_TAG_VIEW,
+ "checkIfSyncIsComplete " + mName + " mSyncReady=" + mSyncReady
+ + " mPendingSyncs=" + mPendingSyncs.size());
+ }
if (!mSyncReady || !mPendingSyncs.isEmpty()) {
if (DEBUG) {
@@ -574,7 +636,7 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
/**
* Create an {@link ITransactionReadyCallback} that the current SurfaceSyncGroup will wait on
* before completing. The caller must ensure that the
- * {@link ITransactionReadyCallback#onTransactionReady(Transaction)} in order for this
+ * {@link ITransactionReadyCallback#onTransactionReady(Transaction)} is called in order for this
* SurfaceSyncGroup to complete.
*
* @param parentSyncGroupMerge true if the ISurfaceSyncGroup is added because its child was
@@ -582,6 +644,7 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
* call newParent.addToSync(oldParent). When this occurs, we need to
* reverse the merge order because the oldParent should always be
* considered older than any other SurfaceSyncGroups.
+ * @hide
*/
public ITransactionReadyCallback createTransactionReadyCallback(boolean parentSyncGroupMerge) {
if (DEBUG) {
@@ -603,9 +666,11 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
mTransaction.merge(t);
}
mPendingSyncs.remove(this);
- Trace.instant(Trace.TRACE_TAG_VIEW,
- "onTransactionReady group=" + mName + " callback="
- + hashCode());
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.instant(Trace.TRACE_TAG_VIEW,
+ "onTransactionReady group=" + mName + " callback="
+ + hashCode());
+ }
checkIfSyncIsComplete();
}
}
@@ -618,19 +683,50 @@ public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
return null;
}
mPendingSyncs.add(transactionReadyCallback);
- Trace.instant(Trace.TRACE_TAG_VIEW,
- "createTransactionReadyCallback " + mName + " mPendingSyncs="
- + mPendingSyncs.size() + " transactionReady="
- + transactionReadyCallback.hashCode());
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.instant(Trace.TRACE_TAG_VIEW,
+ "createTransactionReadyCallback " + mName + " mPendingSyncs="
+ + mPendingSyncs.size() + " transactionReady="
+ + transactionReadyCallback.hashCode());
+ }
}
return transactionReadyCallback;
}
+ private class ISurfaceSyncGroupImpl extends ISurfaceSyncGroup.Stub {
+ @Override
+ public boolean onAddedToSyncGroup(IBinder parentSyncGroupToken,
+ boolean parentSyncGroupMerge) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+ "onAddedToSyncGroup token=" + parentSyncGroupToken.hashCode() + " child="
+ + mName);
+ }
+ boolean didAdd = addSyncToWm(parentSyncGroupToken, parentSyncGroupMerge, null);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ return didAdd;
+ }
+
+ @Override
+ public boolean addToSync(ISurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge) {
+ return SurfaceSyncGroup.this.add(surfaceSyncGroup, parentSyncGroupMerge,
+ null /* runnable */);
+ }
+
+ SurfaceSyncGroup getSurfaceSyncGroup() {
+ return SurfaceSyncGroup.this;
+ }
+ }
+
/**
* A frame callback that is used to synchronize SurfaceViews. The owner of the SurfaceView must
* implement onFrameStarted when trying to sync the SurfaceView. This is to ensure the sync
* knows when the frame is ready to add to the sync.
+ *
+ * @hide
*/
public interface SurfaceViewFrameCallback {
/**
diff --git a/core/java/android/window/SurfaceSyncGroup.md b/core/java/android/window/SurfaceSyncGroup.md
index 406c230ad920..04577a26190f 100644
--- a/core/java/android/window/SurfaceSyncGroup.md
+++ b/core/java/android/window/SurfaceSyncGroup.md
@@ -2,7 +2,7 @@
### Overview
-A generic way for data to be gathered so multiple surfaces can be synced. This is intended to be used with Views, SurfaceView, and any other surface that wants to be involved in a sync. This allows different parts of the Android system to synchronize different windows and layers themselves.
+A generic way for data to be gathered so multiple surfaces can be synced. This is intended to be used with AttachedSurfaceControl, SurfaceView, SurfaceControlViewHost, and any other surface that wants to be involved in a sync. This allows different parts of the Android system to synchronize different windows and layers themselves.
### Code
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 981fbe78032a..2f514795f342 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -48,6 +48,7 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
@@ -165,9 +166,17 @@ public class LockPatternUtils {
private static final String KNOWN_TRUST_AGENTS = "lockscreen.knowntrustagents";
private static final String IS_TRUST_USUALLY_MANAGED = "lockscreen.istrustusuallymanaged";
+ private static final String AUTO_PIN_CONFIRM = "lockscreen.auto_pin_confirm";
+
public static final String CURRENT_LSKF_BASED_PROTECTOR_ID_KEY = "sp-handle";
public static final String PASSWORD_HISTORY_DELIMITER = ",";
+ /**
+ * drives the pin auto confirmation feature availability in code logic.
+ */
+ public static final String FLAG_ENABLE_AUTO_PIN_CONFIRMATION =
+ "AutoPinConfirmation__enable_auto_pin_confirmation";
+
@UnsupportedAppUsage
private final Context mContext;
@UnsupportedAppUsage
@@ -647,6 +656,38 @@ public class LockPatternUtils {
|| isDemoUser;
}
+ /**
+ * Sets the pin auto confirm capability to enabled or disabled
+ * @param enabled enables pin auto confirm capability when true
+ * @param userId user ID of the user this has effect on
+ */
+ public void setAutoPinConfirm(boolean enabled, int userId) {
+ setBoolean(AUTO_PIN_CONFIRM, enabled, userId);
+ }
+
+ /**
+ * Determines if the auto pin confirmation feature is enabled or not for current user
+ * If setting is not available, the default behaviour is disabled
+ * @param userId user ID of the user this has effect on
+ *
+ * @return true, if the entered pin should be auto confirmed
+ */
+ public boolean isAutoPinConfirmEnabled(int userId) {
+ return getBoolean(AUTO_PIN_CONFIRM, /* defaultValue= */ false, userId);
+ }
+
+ /**
+ * Whether the auto pin feature logic is available or not.
+ * @return true, if deviceConfig flag is set to true or the flag is not propagated and
+ * defaultValue is true.
+ */
+ public boolean isAutoPinConfirmFeatureAvailable() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION,
+ FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
+ /* defaultValue= */ false);
+ }
+
/** Returns if the given quality maps to an alphabetic password */
public static boolean isQualityAlphabeticPassword(int quality) {
return quality >= PASSWORD_QUALITY_ALPHABETIC;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 36ce0a69906a..a3e05f2cf859 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -64,7 +64,6 @@ import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackg
import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
-import static com.android.wm.shell.transition.TransitionAnimationHelper.sDisableCustomTaskAnimationProperty;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -568,7 +567,6 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
final boolean isTask = change.getTaskInfo() != null;
final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
final int overrideType = options != null ? options.getType() : ANIM_NONE;
- final boolean canCustomContainer = !isTask || !sDisableCustomTaskAnimationProperty;
final Rect endBounds = Transitions.isClosingType(changeMode)
? mRotator.getEndBoundsInStartRotation(change)
: change.getEndAbsBounds();
@@ -591,7 +589,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
} else if (type == TRANSIT_RELAUNCH) {
a = mTransitionAnimation.createRelaunchAnimation(endBounds, mInsets, endBounds);
} else if (overrideType == ANIM_CUSTOM
- && (canCustomContainer || options.getOverrideTaskTransition())) {
+ && (!isTask || options.getOverrideTaskTransition())) {
a = mTransitionAnimation.loadAnimationRes(options.getPackageName(), enter
? options.getEnterResId() : options.getExitResId());
} else if (overrideType == ANIM_OPEN_CROSS_PROFILE_APPS && enter) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index 6af81f1eb707..1549355c6b2b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -43,7 +43,6 @@ import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Shader;
-import android.os.SystemProperties;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.animation.Animation;
@@ -59,21 +58,6 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup;
/** The helper class that provides methods for adding styles to transition animations. */
public class TransitionAnimationHelper {
- /**
- * Restrict ability of activities overriding transition animation in a way such that
- * an activity can do it only when the transition happens within a same task.
- *
- * @see android.app.Activity#overridePendingTransition(int, int)
- */
- private static final String DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY =
- "persist.wm.disable_custom_task_animation";
-
- /**
- * @see #DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY
- */
- static final boolean sDisableCustomTaskAnimationProperty =
- SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);
-
/** Loads the animation that is defined through attribute id for the given transition. */
@Nullable
public static Animation loadAttributeAnimation(@NonNull TransitionInfo info,
@@ -86,7 +70,6 @@ public class TransitionAnimationHelper {
final boolean isTask = change.getTaskInfo() != null;
final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
final int overrideType = options != null ? options.getType() : ANIM_NONE;
- final boolean canCustomContainer = !isTask || !sDisableCustomTaskAnimationProperty;
final boolean isDream =
isTask && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_DREAM;
int animAttr = 0;
@@ -158,7 +141,7 @@ public class TransitionAnimationHelper {
Animation a = null;
if (animAttr != 0) {
- if (overrideType == ANIM_FROM_STYLE && canCustomContainer) {
+ if (overrideType == ANIM_FROM_STYLE && !isTask) {
a = transitionAnimation
.loadAnimationAttr(options.getPackageName(), options.getAnimations(),
animAttr, translucent);
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index ae88ed7c99b9..e8b5b19daad0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -50,6 +50,8 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -61,6 +63,7 @@ import androidx.compose.ui.layout.AlignmentLine
import androidx.compose.ui.layout.LastBaseline
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.text.TextStyle
@@ -116,7 +119,7 @@ internal fun CustomizedLargeTopAppBar(
actions = actions,
colors = topAppBarColors(),
windowInsets = TopAppBarDefaults.windowInsets,
- maxHeight = 176.dp,
+ maxHeightWithoutTitle = 120.dp,
pinnedHeight = ContainerHeight,
scrollBehavior = scrollBehavior,
)
@@ -258,8 +261,8 @@ private fun SingleRowTopAppBar(
* A two-rows top app bar that is designed to be called by the Large and Medium top app bar
* composables.
*
- * @throws [IllegalArgumentException] if the given [maxHeight] is equal or smaller than the
- * [pinnedHeight]
+ * @throws [IllegalArgumentException] if the given [maxHeightWithoutTitle] is equal or smaller than
+ * the [pinnedHeight]
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -274,29 +277,31 @@ private fun TwoRowsTopAppBar(
actions: @Composable RowScope.() -> Unit,
windowInsets: WindowInsets,
colors: TopAppBarColors,
- maxHeight: Dp,
+ maxHeightWithoutTitle: Dp,
pinnedHeight: Dp,
scrollBehavior: TopAppBarScrollBehavior?
) {
- if (maxHeight <= pinnedHeight) {
+ if (maxHeightWithoutTitle <= pinnedHeight) {
throw IllegalArgumentException(
"A TwoRowsTopAppBar max height should be greater than its pinned height"
)
}
val pinnedHeightPx: Float
- val maxHeightPx: Float
+ val density = LocalDensity.current
+ val maxHeightPx = density.run {
+ remember { mutableStateOf((maxHeightWithoutTitle + pinnedHeight).toPx()) }
+ }
val titleBottomPaddingPx: Int
- LocalDensity.current.run {
+ density.run {
pinnedHeightPx = pinnedHeight.toPx()
- maxHeightPx = maxHeight.toPx()
titleBottomPaddingPx = titleBottomPadding.roundToPx()
}
// Sets the app bar's height offset limit to hide just the bottom title area and keep top title
// visible when collapsed.
SideEffect {
- if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx) {
- scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx
+ if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx.value) {
+ scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx.value
}
}
@@ -366,12 +371,19 @@ private fun TwoRowsTopAppBar(
)
TopAppBarLayout(
modifier = Modifier.clipToBounds(),
- heightPx = maxHeightPx - pinnedHeightPx + (scrollBehavior?.state?.heightOffset
- ?: 0f),
+ heightPx = maxHeightPx.value - pinnedHeightPx +
+ (scrollBehavior?.state?.heightOffset ?: 0f),
navigationIconContentColor = colors.navigationIconContentColor,
titleContentColor = colors.titleContentColor,
actionIconContentColor = colors.actionIconContentColor,
- title = title,
+ title = {
+ Box(modifier = Modifier.onGloballyPositioned { coordinates ->
+ density.run {
+ maxHeightPx.value =
+ maxHeightWithoutTitle.toPx() + coordinates.size.height.toFloat()
+ }
+ }) { title() }
+ },
titleTextStyle = titleTextStyle,
titleAlpha = bottomTitleAlpha,
titleVerticalArrangement = Arrangement.Bottom,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index c729b09628af..17a94b8639d0 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -41,6 +41,7 @@ import androidx.annotation.BinderThread
import androidx.annotation.UiThread
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
+import java.lang.IllegalArgumentException
import kotlin.math.roundToInt
private const val TAG = "ActivityLaunchAnimator"
@@ -338,13 +339,24 @@ class ActivityLaunchAnimator(
* Return a [Controller] that will animate and expand [view] into the opening window.
*
* Important: The view must be attached to a [ViewGroup] when calling this function and
- * during the animation. For safety, this method will return null when it is not.
+ * during the animation. For safety, this method will return null when it is not. The
+ * view must also implement [LaunchableView], otherwise this method will throw.
*
* Note: The background of [view] should be a (rounded) rectangle so that it can be
* properly animated.
*/
@JvmStatic
fun fromView(view: View, cujType: Int? = null): Controller? {
+ // Make sure the View we launch from implements LaunchableView to avoid visibility
+ // issues.
+ if (view !is LaunchableView) {
+ throw IllegalArgumentException(
+ "An ActivityLaunchAnimator.Controller was created from a View that does " +
+ "not implement LaunchableView. This can lead to subtle bugs where the" +
+ " visibility of the View we are launching from is not what we expected."
+ )
+ }
+
if (view.parent !is ViewGroup) {
Log.e(
TAG,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index e91a6710ace5..b8d78fb208f4 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -40,6 +40,7 @@ import com.android.systemui.animation.back.BackAnimationSpec
import com.android.systemui.animation.back.applyTo
import com.android.systemui.animation.back.floatingSystemSurfacesForSysUi
import com.android.systemui.animation.back.onBackAnimationCallbackFrom
+import java.lang.IllegalArgumentException
import kotlin.math.roundToInt
private const val TAG = "DialogLaunchAnimator"
@@ -157,12 +158,23 @@ constructor(
* Create a [Controller] that can animate [source] to and from a dialog.
*
* Important: The view must be attached to a [ViewGroup] when calling this function and
- * during the animation. For safety, this method will return null when it is not.
+ * during the animation. For safety, this method will return null when it is not. The
+ * view must also implement [LaunchableView], otherwise this method will throw.
*
* Note: The background of [view] should be a (rounded) rectangle so that it can be
* properly animated.
*/
fun fromView(source: View, cuj: DialogCuj? = null): Controller? {
+ // Make sure the View we launch from implements LaunchableView to avoid visibility
+ // issues.
+ if (source !is LaunchableView) {
+ throw IllegalArgumentException(
+ "A DialogLaunchAnimator.Controller was created from a View that does not " +
+ "implement LaunchableView. This can lead to subtle bugs where the " +
+ "visibility of the View we are launching from is not what we expected."
+ )
+ }
+
if (source.parent !is ViewGroup) {
Log.e(
TAG,
@@ -249,23 +261,6 @@ constructor(
}
?: controller
- if (
- animatedParent == null &&
- controller is ViewDialogLaunchAnimatorController &&
- controller.source !is LaunchableView
- ) {
- // Make sure the View we launch from implements LaunchableView to avoid visibility
- // issues. Given that we don't own dialog decorViews so we can't enforce it for launches
- // from a dialog.
- // TODO(b/243636422): Throw instead of logging to enforce this.
- Log.w(
- TAG,
- "A dialog was launched from a View that does not implement LaunchableView. This " +
- "can lead to subtle bugs where the visibility of the View we are " +
- "launching from is not what we expected."
- )
- }
-
// Make sure we don't run the launch animation from the same source twice at the same time.
if (openedDialogs.any { it.controller.sourceIdentity == controller.sourceIdentity }) {
Log.e(
@@ -613,10 +608,16 @@ private class AnimatedDialog(
}
// Animate that view with the background. Throw if we didn't find one, because
- // otherwise
- // it's not clear what we should animate.
+ // otherwise it's not clear what we should animate.
+ if (viewGroupWithBackground == null) {
+ error("Unable to find ViewGroup with background")
+ }
+
+ if (viewGroupWithBackground !is LaunchableView) {
+ error("The animated ViewGroup with background must implement LaunchableView")
+ }
+
viewGroupWithBackground
- ?: throw IllegalStateException("Unable to find ViewGroup with background")
} else {
// We will make the dialog window (and therefore its DecorView) fullscreen to make
// it possible to animate outside its bounds.
@@ -639,7 +640,7 @@ private class AnimatedDialog(
FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
)
- val dialogContentWithBackground = FrameLayout(dialog.context)
+ val dialogContentWithBackground = LaunchableFrameLayout(dialog.context)
dialogContentWithBackground.background = decorView.background
// Make the window background transparent. Note that setting the window (or
@@ -720,7 +721,10 @@ private class AnimatedDialog(
// Make the background view invisible until we start the animation. We use the transition
// visibility like GhostView does so that we don't mess up with the accessibility tree (see
- // b/204944038#comment17).
+ // b/204944038#comment17). Given that this background implements LaunchableView, we call
+ // setShouldBlockVisibilityChanges() early so that the current visibility (VISIBLE) is
+ // restored at the end of the animation.
+ dialogContentWithBackground.setShouldBlockVisibilityChanges(true)
dialogContentWithBackground.setTransitionVisibility(View.INVISIBLE)
// Make sure the dialog is visible instantly and does not do any window animation.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 26aa0e85cbd2..23e3a01c2686 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -34,6 +34,7 @@ import android.view.ViewGroup
import android.view.ViewGroupOverlay
import android.widget.FrameLayout
import com.android.internal.jank.InteractionJankMonitor
+import java.lang.IllegalArgumentException
import java.util.LinkedList
import kotlin.math.min
import kotlin.math.roundToInt
@@ -46,7 +47,8 @@ private const val TAG = "GhostedViewLaunchAnimatorController"
* of the ghosted view.
*
* Important: [ghostedView] must be attached to a [ViewGroup] when calling this function and during
- * the animation.
+ * the animation. It must also implement [LaunchableView], otherwise an exception will be thrown
+ * during this controller instantiation.
*
* Note: Avoid instantiating this directly and call [ActivityLaunchAnimator.Controller.fromView]
* whenever possible instead.
@@ -101,6 +103,15 @@ constructor(
private val background: Drawable?
init {
+ // Make sure the View we launch from implements LaunchableView to avoid visibility issues.
+ if (ghostedView !is LaunchableView) {
+ throw IllegalArgumentException(
+ "A GhostedViewLaunchAnimatorController was created from a View that does not " +
+ "implement LaunchableView. This can lead to subtle bugs where the visibility " +
+ "of the View we are launching from is not what we expected."
+ )
+ }
+
/** Find the first view with a background in [view] and its children. */
fun findBackground(view: View): Drawable? {
if (view.background != null) {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.kt
new file mode 100644
index 000000000000..2eb503b43cc5
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.animation
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.FrameLayout
+
+/** A [FrameLayout] that also implements [LaunchableView]. */
+open class LaunchableFrameLayout : FrameLayout, LaunchableView {
+ private val delegate =
+ LaunchableViewDelegate(
+ this,
+ superSetVisibility = { super.setVisibility(it) },
+ )
+
+ constructor(context: Context) : super(context)
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ defStyleAttr: Int
+ ) : super(context, attrs, defStyleAttr)
+
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ defStyleAttr: Int,
+ defStyleRes: Int
+ ) : super(context, attrs, defStyleAttr, defStyleRes)
+
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {
+ delegate.setShouldBlockVisibilityChanges(block)
+ }
+
+ override fun setVisibility(visibility: Int) {
+ delegate.setVisibility(visibility)
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
index 9257f99efe96..46d5a5c0af8c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
@@ -25,7 +25,7 @@ import com.android.internal.jank.InteractionJankMonitor
/** A [DialogLaunchAnimator.Controller] that can animate a [View] from/to a dialog. */
class ViewDialogLaunchAnimatorController
internal constructor(
- internal val source: View,
+ private val source: View,
override val cuj: DialogCuj?,
) : DialogLaunchAnimator.Controller {
override val viewRoot: ViewRootImpl?
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt
index fd9355d6a77d..e4f6db57f689 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt
@@ -30,8 +30,8 @@ object ViewRootSync {
val syncGroup = SurfaceSyncGroup("SysUIAnimation")
syncGroup.addSyncCompleteCallback(view.context.mainExecutor) { then() }
- syncGroup.addToSync(view.rootSurfaceControl)
- syncGroup.addToSync(otherView.rootSurfaceControl)
+ syncGroup.add(view.rootSurfaceControl, null /* runnable */)
+ syncGroup.add(otherView.rootSurfaceControl, null /* runnable */)
syncGroup.markSyncReady()
}
diff --git a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
index de96e9765668..446bb0122630 100644
--- a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
@@ -20,7 +20,7 @@
android:layout_width="wrap_content"
android:paddingVertical="@dimen/dream_overlay_complication_home_controls_padding">
- <ImageView
+ <com.android.systemui.common.ui.view.LaunchableImageView
android:id="@+id/home_controls_chip"
android:layout_height="@dimen/keyguard_affordance_fixed_height"
android:layout_width="@dimen/keyguard_affordance_fixed_width"
diff --git a/packages/SystemUI/res/layout/global_actions_grid_lite.xml b/packages/SystemUI/res/layout/global_actions_grid_lite.xml
index 5588fd391681..a64c9aebec3e 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_lite.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_lite.xml
@@ -33,7 +33,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_weight="1">
- <androidx.constraintlayout.widget.ConstraintLayout
+ <com.android.systemui.common.ui.view.LaunchableConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@android:id/list"
@@ -55,6 +55,6 @@
app:flow_horizontalGap="@dimen/global_actions_lite_padding"
app:flow_verticalGap="@dimen/global_actions_lite_padding"
app:flow_horizontalStyle="packed"/>
- </androidx.constraintlayout.widget.ConstraintLayout>
+ </com.android.systemui.common.ui.view.LaunchableConstraintLayout>
</com.android.systemui.globalactions.GlobalActionsLayoutLite>
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
index 6f3362308484..07c428b5dd7a 100644
--- a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
+++ b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
@@ -24,7 +24,7 @@
android:layout_gravity="end">
<!-- We add a background behind the UserAvatarView with the same color and with a circular shape
so that this view can be expanded into a Dialog or an Activity. -->
- <FrameLayout
+ <com.android.systemui.animation.LaunchableFrameLayout
android:id="@+id/kg_multi_user_avatar_with_background"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -42,5 +42,5 @@
systemui:framePadding="0dp"
systemui:frameWidth="0dp">
</com.android.systemui.statusbar.phone.UserAvatarView>
- </FrameLayout>
+ </com.android.systemui.animation.LaunchableFrameLayout>
</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_recommendation_view.xml b/packages/SystemUI/res/layout/media_recommendation_view.xml
new file mode 100644
index 000000000000..101fad97beb5
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_recommendation_view.xml
@@ -0,0 +1,65 @@
+<?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
+ -->
+<!-- Layout for media recommendation item inside QSPanel carousel -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Album cover -->
+ <ImageView
+ android:id="@+id/media_cover"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:translationZ="0dp"
+ android:scaleType="centerCrop"
+ android:adjustViewBounds="true"
+ android:clipToOutline="true"
+ android:background="@drawable/bg_smartspace_media_item"/>
+
+ <!-- App icon -->
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/media_rec_app_icon"
+ android:layout_width="@dimen/qs_media_rec_icon_top_margin"
+ android:layout_height="@dimen/qs_media_rec_icon_top_margin"
+ android:layout_marginStart="@dimen/qs_media_info_spacing"
+ android:layout_marginTop="@dimen/qs_media_info_spacing"/>
+
+ <!-- Artist name -->
+ <TextView
+ android:id="@+id/media_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/qs_media_info_spacing"
+ android:layout_marginEnd="@dimen/qs_media_info_spacing"
+ android:layout_marginBottom="@dimen/qs_media_rec_album_title_bottom_margin"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+ android:singleLine="true"
+ android:textSize="12sp"
+ android:gravity="top"
+ android:layout_gravity="bottom"/>
+
+ <!-- Album name -->
+ <TextView
+ android:id="@+id/media_subtitle"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_media_rec_album_subtitle_height"
+ android:layout_marginEnd="@dimen/qs_media_info_spacing"
+ android:layout_marginStart="@dimen/qs_media_info_spacing"
+ android:layout_marginBottom="@dimen/qs_media_info_spacing"
+ android:fontFamily="@*android:string/config_headlineFontFamily"
+ android:singleLine="true"
+ android:textSize="11sp"
+ android:gravity="center_vertical"
+ android:layout_gravity="bottom"/>
+</merge> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_recommendations.xml b/packages/SystemUI/res/layout/media_recommendations.xml
new file mode 100644
index 000000000000..65fc19c5b2a4
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_recommendations.xml
@@ -0,0 +1,75 @@
+<?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
+ -->
+
+<!-- Layout for media recommendations inside QSPanel carousel -->
+<com.android.systemui.util.animation.TransitionLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/media_recommendations_updated"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:forceHasOverlappingRendering="false"
+ android:background="@drawable/qs_media_background"
+ android:theme="@style/MediaPlayer">
+
+ <!-- This view just ensures the full media player is a certain height. -->
+ <View
+ android:id="@+id/sizing_view"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_media_session_height_expanded" />
+
+ <TextView
+ android:id="@+id/media_rec_title"
+ style="@style/MediaPlayer.Recommendation.Header"
+ android:text="@string/controls_media_smartspace_rec_header"/>
+
+ <FrameLayout
+ android:id="@+id/media_cover1_container"
+ style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+ >
+
+ <include
+ layout="@layout/media_recommendation_view"/>
+
+ </FrameLayout>
+
+
+ <FrameLayout
+ android:id="@+id/media_cover2_container"
+ style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+ >
+
+ <include
+ layout="@layout/media_recommendation_view"/>
+
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/media_cover3_container"
+ style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+ >
+
+ <include
+ layout="@layout/media_recommendation_view"/>
+
+ </FrameLayout>
+
+ <include
+ layout="@layout/media_long_press_menu" />
+
+</com.android.systemui.util.animation.TransitionLayout>
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index abc83379950a..f2e114b511bc 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -106,7 +106,7 @@
app:layout_constrainedWidth="true"
app:layout_constraintWidth_min="@dimen/min_clickable_item_size"
app:layout_constraintHeight_min="@dimen/min_clickable_item_size">
- <LinearLayout
+ <com.android.systemui.common.ui.view.LaunchableLinearLayout
android:id="@+id/media_seamless_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -135,7 +135,7 @@
android:textDirection="locale"
android:textSize="12sp"
android:lineHeight="16sp" />
- </LinearLayout>
+ </com.android.systemui.common.ui.view.LaunchableLinearLayout>
</LinearLayout>
<!-- Song name -->
diff --git a/packages/SystemUI/res/layout/ongoing_call_chip.xml b/packages/SystemUI/res/layout/ongoing_call_chip.xml
index c949ba0db171..18d231c5d32f 100644
--- a/packages/SystemUI/res/layout/ongoing_call_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_call_chip.xml
@@ -23,7 +23,7 @@
android:layout_gravity="center_vertical|start"
android:layout_marginStart="5dp"
>
- <LinearLayout
+ <com.android.systemui.common.ui.view.LaunchableLinearLayout
android:id="@+id/ongoing_call_chip_background"
android:layout_width="wrap_content"
android:layout_height="@dimen/ongoing_appops_chip_height"
@@ -55,5 +55,5 @@
android:textColor="?android:attr/colorPrimary"
/>
- </LinearLayout>
+ </com.android.systemui.common.ui.view.LaunchableLinearLayout>
</FrameLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a1b8fe5c86be..9e001e006803 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1068,8 +1068,13 @@
<!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
<dimen name="qs_media_rec_icon_top_margin">16dp</dimen>
<dimen name="qs_media_rec_album_size">88dp</dimen>
+ <dimen name="qs_media_rec_album_width">110dp</dimen>
+ <dimen name="qs_media_rec_album_height_expanded">108dp</dimen>
+ <dimen name="qs_media_rec_album_height_collapsed">77dp</dimen>
<dimen name="qs_media_rec_album_side_margin">16dp</dimen>
<dimen name="qs_media_rec_album_bottom_margin">8dp</dimen>
+ <dimen name="qs_media_rec_album_title_bottom_margin">22dp</dimen>
+ <dimen name="qs_media_rec_album_subtitle_height">12dp</dimen>
<!-- Media tap-to-transfer chip for sender device -->
<dimen name="media_ttt_chip_outer_padding">16dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d14207a2d104..575dc940ec4d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2464,6 +2464,8 @@
<string name="controls_media_smartspace_rec_item_description">Play <xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> by <xliff:g id="artist_name" example="Various artists">%2$s</xliff:g> from <xliff:g id="app_label" example="Spotify">%3$s</xliff:g></string>
<!-- Description for Smartspace recommendation's media item which doesn't have artist info, including information for the media's title and the source app [CHAR LIMIT=NONE]-->
<string name="controls_media_smartspace_rec_item_no_artist_description">Play <xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> from <xliff:g id="app_label" example="Spotify">%2$s</xliff:g></string>
+ <!-- Header title for Smartspace recommendation card within media controls. [CHAR_LIMIT=30] -->
+ <string name="controls_media_smartspace_rec_header">For You</string>
<!--- ****** Media tap-to-transfer ****** -->
<!-- Text for a button to undo the media transfer. [CHAR LIMIT=20] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index f8f5e8365165..dd87e914eefe 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -678,6 +678,17 @@
<style name="MediaPlayer.Recommendation"/>
+ <style name="MediaPlayer.Recommendation.Header">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginTop">@dimen/qs_media_padding</item>
+ <item name="android:layout_marginStart">@dimen/qs_media_padding</item>
+ <item name="android:fontFamily">=@*android:string/config_headlineFontFamilyMedium</item>
+ <item name="android:singleLine">true</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
+
<style name="MediaPlayer.Recommendation.AlbumContainer">
<item name="android:layout_width">@dimen/qs_media_rec_album_size</item>
<item name="android:layout_height">@dimen/qs_media_rec_album_size</item>
@@ -686,6 +697,12 @@
<item name="android:layout_marginBottom">@dimen/qs_media_rec_album_bottom_margin</item>
</style>
+ <style name="MediaPlayer.Recommendation.AlbumContainer.Updated">
+ <item name="android:layout_width">@dimen/qs_media_rec_album_width</item>
+ <item name="android:background">@drawable/qs_media_light_source</item>
+ <item name="android:layout_marginTop">@dimen/qs_media_info_spacing</item>
+ </style>
+
<style name="MediaPlayer.Recommendation.Album">
<item name="android:backgroundTint">@color/media_player_album_bg</item>
</style>
diff --git a/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml b/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml
new file mode 100644
index 000000000000..d3be3c7de5ad
--- /dev/null
+++ b/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml
@@ -0,0 +1,64 @@
+<?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
+ -->
+<ConstraintSet
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ >
+
+ <Constraint
+ android:id="@+id/sizing_view"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_media_session_height_collapsed"
+ />
+
+ <Constraint
+ android:id="@+id/media_rec_title"
+ style="@style/MediaPlayer.Recommendation.Header"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"/>
+
+ <Constraint
+ android:id="@+id/media_cover1_container"
+ style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+ android:layout_height="@dimen/qs_media_rec_album_height_collapsed"
+ android:layout_marginEnd="@dimen/qs_media_info_spacing"
+ android:layout_marginStart="@dimen/qs_media_padding"
+ app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/media_cover2_container"/>
+
+
+ <Constraint
+ android:id="@+id/media_cover2_container"
+ style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+ android:layout_height="@dimen/qs_media_rec_album_height_collapsed"
+ android:layout_marginEnd="@dimen/qs_media_info_spacing"
+ app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+ app:layout_constraintStart_toEndOf="@id/media_cover1_container"
+ app:layout_constraintEnd_toStartOf="@id/media_cover3_container"/>
+
+ <Constraint
+ android:id="@+id/media_cover3_container"
+ style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+ android:layout_height="@dimen/qs_media_rec_album_height_collapsed"
+ android:layout_marginEnd="@dimen/qs_media_padding"
+ app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+ app:layout_constraintStart_toEndOf="@id/media_cover2_container"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+
+</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml b/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml
new file mode 100644
index 000000000000..88c70552e9e8
--- /dev/null
+++ b/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml
@@ -0,0 +1,71 @@
+<?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
+ -->
+<ConstraintSet
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ >
+
+ <Constraint
+ android:id="@+id/sizing_view"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_media_session_height_expanded"
+ />
+
+ <Constraint
+ android:id="@+id/media_rec_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/qs_media_padding"
+ android:layout_marginStart="@dimen/qs_media_padding"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+ android:singleLine="true"
+ android:textSize="14sp"
+ android:textColor="@color/notification_primary_text_color"/>
+
+ <Constraint
+ android:id="@+id/media_cover1_container"
+ style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+ android:layout_height="@dimen/qs_media_rec_album_height_expanded"
+ android:layout_marginEnd="@dimen/qs_media_info_spacing"
+ android:layout_marginStart="@dimen/qs_media_padding"
+ app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/media_cover2_container"/>
+
+
+ <Constraint
+ android:id="@+id/media_cover2_container"
+ style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+ android:layout_height="@dimen/qs_media_rec_album_height_expanded"
+ android:layout_marginEnd="@dimen/qs_media_info_spacing"
+ app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+ app:layout_constraintStart_toEndOf="@id/media_cover1_container"
+ app:layout_constraintEnd_toStartOf="@id/media_cover3_container"/>
+
+ <Constraint
+ android:id="@+id/media_cover3_container"
+ style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+ android:layout_height="@dimen/qs_media_rec_album_height_expanded"
+ android:layout_marginEnd="@dimen/qs_media_padding"
+ app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+ app:layout_constraintStart_toEndOf="@id/media_cover2_container"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+
+</ConstraintSet>
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableConstraintLayout.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableConstraintLayout.kt
new file mode 100644
index 000000000000..9763665c5b7c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableConstraintLayout.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.android.systemui.animation.LaunchableView
+import com.android.systemui.animation.LaunchableViewDelegate
+
+/** A [ConstraintLayout] that also implements [LaunchableView]. */
+open class LaunchableConstraintLayout : ConstraintLayout, LaunchableView {
+ private val delegate =
+ LaunchableViewDelegate(
+ this,
+ superSetVisibility = { super.setVisibility(it) },
+ )
+
+ constructor(context: Context) : super(context)
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ defStyleAttr: Int
+ ) : super(context, attrs, defStyleAttr)
+
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ defStyleAttr: Int,
+ defStyleRes: Int
+ ) : super(context, attrs, defStyleAttr, defStyleRes)
+
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {
+ delegate.setShouldBlockVisibilityChanges(block)
+ }
+
+ override fun setVisibility(visibility: Int) {
+ delegate.setVisibility(visibility)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
index ddde6280f3a2..2edac528b037 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
@@ -23,7 +23,7 @@ import com.android.systemui.animation.LaunchableView
import com.android.systemui.animation.LaunchableViewDelegate
/** A [LinearLayout] that also implements [LaunchableView]. */
-class LaunchableLinearLayout : LinearLayout, LaunchableView {
+open class LaunchableLinearLayout : LinearLayout, LaunchableView {
private val delegate =
LaunchableViewDelegate(
this,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index a6977e1fa8dd..182f4c469b3a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -364,6 +364,10 @@ object Flags {
// TODO(b/266157412): Tracking Bug
val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions")
+ // TODO(b/266739309): Tracking Bug
+ @JvmField
+ val MEDIA_RECOMMENDATION_CARD_UPDATE = unreleasedFlag(914, "media_recommendation_card_update")
+
// 1000 - dock
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
index 1a10b18a5a69..8c1ec166fb2e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
@@ -21,6 +21,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
+import com.android.internal.widget.CachingIconView
import com.android.systemui.R
import com.android.systemui.media.controls.models.GutsViewHolder
import com.android.systemui.media.controls.ui.IlluminationDrawable
@@ -29,18 +30,15 @@ import com.android.systemui.util.animation.TransitionLayout
private const val TAG = "RecommendationViewHolder"
/** ViewHolder for a Smartspace media recommendation. */
-class RecommendationViewHolder private constructor(itemView: View) {
+class RecommendationViewHolder private constructor(itemView: View, updatedView: Boolean) {
val recommendations = itemView as TransitionLayout
// Recommendation screen
- val cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
- val mediaCoverItems =
- listOf<ImageView>(
- itemView.requireViewById(R.id.media_cover1),
- itemView.requireViewById(R.id.media_cover2),
- itemView.requireViewById(R.id.media_cover3)
- )
+ lateinit var cardIcon: ImageView
+ lateinit var mediaAppIcons: List<CachingIconView>
+ lateinit var cardTitle: TextView
+
val mediaCoverContainers =
listOf<ViewGroup>(
itemView.requireViewById(R.id.media_cover1_container),
@@ -48,21 +46,45 @@ class RecommendationViewHolder private constructor(itemView: View) {
itemView.requireViewById(R.id.media_cover3_container)
)
val mediaTitles: List<TextView> =
- listOf(
- itemView.requireViewById(R.id.media_title1),
- itemView.requireViewById(R.id.media_title2),
- itemView.requireViewById(R.id.media_title3)
- )
+ if (updatedView) {
+ mediaCoverContainers.map { it.requireViewById(R.id.media_title) }
+ } else {
+ listOf(
+ itemView.requireViewById(R.id.media_title1),
+ itemView.requireViewById(R.id.media_title2),
+ itemView.requireViewById(R.id.media_title3)
+ )
+ }
val mediaSubtitles: List<TextView> =
- listOf(
- itemView.requireViewById(R.id.media_subtitle1),
- itemView.requireViewById(R.id.media_subtitle2),
- itemView.requireViewById(R.id.media_subtitle3)
- )
+ if (updatedView) {
+ mediaCoverContainers.map { it.requireViewById(R.id.media_subtitle) }
+ } else {
+ listOf(
+ itemView.requireViewById(R.id.media_subtitle1),
+ itemView.requireViewById(R.id.media_subtitle2),
+ itemView.requireViewById(R.id.media_subtitle3)
+ )
+ }
+ val mediaCoverItems: List<ImageView> =
+ if (updatedView) {
+ mediaCoverContainers.map { it.requireViewById(R.id.media_cover) }
+ } else {
+ listOf(
+ itemView.requireViewById(R.id.media_cover1),
+ itemView.requireViewById(R.id.media_cover2),
+ itemView.requireViewById(R.id.media_cover3)
+ )
+ }
val gutsViewHolder = GutsViewHolder(itemView)
init {
+ if (updatedView) {
+ mediaAppIcons = mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) }
+ cardTitle = itemView.requireViewById(R.id.media_rec_title)
+ } else {
+ cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
+ }
(recommendations.background as IlluminationDrawable).let { background ->
mediaCoverContainers.forEach { background.registerLightSource(it) }
background.registerLightSource(gutsViewHolder.cancel)
@@ -83,36 +105,52 @@ class RecommendationViewHolder private constructor(itemView: View) {
* @param parent Parent of inflated view.
*/
@JvmStatic
- fun create(inflater: LayoutInflater, parent: ViewGroup): RecommendationViewHolder {
+ fun create(
+ inflater: LayoutInflater,
+ parent: ViewGroup,
+ updatedView: Boolean,
+ ): RecommendationViewHolder {
val itemView =
- inflater.inflate(
- R.layout.media_smartspace_recommendations,
- parent,
- false /* attachToRoot */
- )
+ if (updatedView) {
+ inflater.inflate(
+ R.layout.media_recommendations,
+ parent,
+ false /* attachToRoot */
+ )
+ } else {
+ inflater.inflate(
+ R.layout.media_smartspace_recommendations,
+ parent,
+ false /* attachToRoot */
+ )
+ }
// Because this media view (a TransitionLayout) is used to measure and layout the views
// in various states before being attached to its parent, we can't depend on the default
// LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction.
itemView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
- return RecommendationViewHolder(itemView)
+ return RecommendationViewHolder(itemView, updatedView)
}
// Res Ids for the control components on the recommendation view.
val controlsIds =
setOf(
R.id.recommendation_card_icon,
+ R.id.media_rec_title,
R.id.media_cover1,
R.id.media_cover2,
R.id.media_cover3,
+ R.id.media_cover,
R.id.media_cover1_container,
R.id.media_cover2_container,
R.id.media_cover3_container,
R.id.media_title1,
R.id.media_title2,
R.id.media_title3,
+ R.id.media_title,
R.id.media_subtitle1,
R.id.media_subtitle2,
- R.id.media_subtitle3
+ R.id.media_subtitle3,
+ R.id.media_subtitle,
)
val mediaTitlesAndSubtitlesIds =
@@ -120,9 +158,11 @@ class RecommendationViewHolder private constructor(itemView: View) {
R.id.media_title1,
R.id.media_title2,
R.id.media_title3,
+ R.id.media_title,
R.id.media_subtitle1,
R.id.media_subtitle2,
- R.id.media_subtitle3
+ R.id.media_subtitle3,
+ R.id.media_subtitle,
)
val mediaContainersIds =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index e7f7647797cd..b2ad15522743 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -43,6 +43,7 @@ import com.android.systemui.media.controls.models.recommendation.RecommendationV
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.media.controls.ui.MediaControlPanel.SMARTSPACE_CARD_DISMISS_EVENT
+import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.controls.util.SmallHash
import com.android.systemui.plugins.ActivityStarter
@@ -88,7 +89,8 @@ constructor(
falsingManager: FalsingManager,
dumpManager: DumpManager,
private val logger: MediaUiEventLogger,
- private val debugLogger: MediaCarouselControllerLogger
+ private val debugLogger: MediaCarouselControllerLogger,
+ private val mediaFlags: MediaFlags,
) : Dumpable {
/** The current width of the carousel */
private var currentCarouselWidth: Int = 0
@@ -647,7 +649,11 @@ constructor(
val newRecs = mediaControlPanelFactory.get()
newRecs.attachRecommendation(
- RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent)
+ RecommendationViewHolder.create(
+ LayoutInflater.from(context),
+ mediaContent,
+ mediaFlags.isRecommendationCardUpdateEnabled()
+ )
)
newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
val lp =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 39dd7336e4d1..2fd4f27a5bea 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -62,6 +62,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
+import androidx.appcompat.content.res.AppCompatResources;
import androidx.constraintlayout.widget.ConstraintSet;
import com.android.internal.annotations.VisibleForTesting;
@@ -755,43 +756,16 @@ public class MediaControlPanel {
int width = mMediaViewHolder.getAlbumView().getMeasuredWidth();
int height = mMediaViewHolder.getAlbumView().getMeasuredHeight();
- // WallpaperColors.fromBitmap takes a good amount of time. We do that work
- // on the background executor to avoid stalling animations on the UI Thread.
mBackgroundExecutor.execute(() -> {
// Album art
ColorScheme mutableColorScheme = null;
Drawable artwork;
boolean isArtworkBound;
Icon artworkIcon = data.getArtwork();
- WallpaperColors wallpaperColors = null;
- if (artworkIcon != null) {
- if (artworkIcon.getType() == Icon.TYPE_BITMAP
- || artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
- // Avoids extra processing if this is already a valid bitmap
- wallpaperColors = WallpaperColors
- .fromBitmap(artworkIcon.getBitmap());
- } else {
- Drawable artworkDrawable = artworkIcon.loadDrawable(mContext);
- if (artworkDrawable != null) {
- wallpaperColors = WallpaperColors
- .fromDrawable(artworkIcon.loadDrawable(mContext));
- }
- }
- }
+ WallpaperColors wallpaperColors = getWallpaperColor(artworkIcon);
if (wallpaperColors != null) {
mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
- Drawable albumArt = getScaledBackground(artworkIcon, width, height);
- GradientDrawable gradient = (GradientDrawable) mContext
- .getDrawable(R.drawable.qs_media_scrim);
- gradient.setColors(new int[] {
- ColorUtilKt.getColorWithAlpha(
- MediaColorSchemesKt.backgroundStartFromScheme(mutableColorScheme),
- 0.25f),
- ColorUtilKt.getColorWithAlpha(
- MediaColorSchemesKt.backgroundEndFromScheme(mutableColorScheme),
- 0.9f),
- });
- artwork = new LayerDrawable(new Drawable[] { albumArt, gradient });
+ artwork = addGradientToIcon(artworkIcon, mutableColorScheme, width, height);
isArtworkBound = true;
} else {
// If there's no artwork, use colors from the app icon
@@ -870,6 +844,96 @@ public class MediaControlPanel {
});
}
+ private void bindRecommendationArtwork(
+ SmartspaceAction recommendation,
+ String packageName,
+ int itemIndex
+ ) {
+ final int traceCookie = recommendation.hashCode();
+ final String traceName =
+ "MediaControlPanel#bindRecommendationArtwork<" + packageName + ">";
+ Trace.beginAsyncSection(traceName, traceCookie);
+
+ // Capture width & height from views in foreground for artwork scaling in background
+ int width = mRecommendationViewHolder.getMediaCoverContainers().get(0).getMeasuredWidth();
+ int height = mRecommendationViewHolder.getMediaCoverContainers().get(0).getMeasuredHeight();
+
+ mBackgroundExecutor.execute(() -> {
+ // Album art
+ ColorScheme mutableColorScheme = null;
+ Drawable artwork;
+ Icon artworkIcon = recommendation.getIcon();
+ WallpaperColors wallpaperColors = getWallpaperColor(artworkIcon);
+ if (wallpaperColors != null) {
+ mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
+ artwork = addGradientToIcon(artworkIcon, mutableColorScheme, width, height);
+ } else {
+ artwork = new ColorDrawable(Color.TRANSPARENT);
+ }
+
+ mMainExecutor.execute(() -> {
+ // Bind the artwork drawable to media cover.
+ ImageView mediaCover =
+ mRecommendationViewHolder.getMediaCoverItems().get(itemIndex);
+ mediaCover.setImageDrawable(artwork);
+
+ // Set up the app icon.
+ ImageView appIconView = mRecommendationViewHolder.getMediaAppIcons().get(itemIndex);
+ appIconView.clearColorFilter();
+ try {
+ Drawable icon = mContext.getPackageManager()
+ .getApplicationIcon(packageName);
+ appIconView.setImageDrawable(icon);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Cannot find icon for package " + packageName, e);
+ appIconView.setImageResource(R.drawable.ic_music_note);
+ }
+ Trace.endAsyncSection(traceName, traceCookie);
+ });
+ });
+ }
+
+ // This method should be called from a background thread. WallpaperColors.fromBitmap takes a
+ // good amount of time. We do that work on the background executor to avoid stalling animations
+ // on the UI Thread.
+ private WallpaperColors getWallpaperColor(Icon artworkIcon) {
+ if (artworkIcon != null) {
+ if (artworkIcon.getType() == Icon.TYPE_BITMAP
+ || artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
+ // Avoids extra processing if this is already a valid bitmap
+ return WallpaperColors
+ .fromBitmap(artworkIcon.getBitmap());
+ } else {
+ Drawable artworkDrawable = artworkIcon.loadDrawable(mContext);
+ if (artworkDrawable != null) {
+ return WallpaperColors
+ .fromDrawable(artworkIcon.loadDrawable(mContext));
+ }
+ }
+ }
+ return null;
+ }
+
+ private LayerDrawable addGradientToIcon(
+ Icon artworkIcon,
+ ColorScheme mutableColorScheme,
+ int width,
+ int height
+ ) {
+ Drawable albumArt = getScaledBackground(artworkIcon, width, height);
+ GradientDrawable gradient = (GradientDrawable) AppCompatResources
+ .getDrawable(mContext, R.drawable.qs_media_scrim);
+ gradient.setColors(new int[] {
+ ColorUtilKt.getColorWithAlpha(
+ MediaColorSchemesKt.backgroundStartFromScheme(mutableColorScheme),
+ 0.25f),
+ ColorUtilKt.getColorWithAlpha(
+ MediaColorSchemesKt.backgroundEndFromScheme(mutableColorScheme),
+ 0.9f),
+ });
+ return new LayerDrawable(new Drawable[] { albumArt, gradient });
+ }
+
private void scaleTransitionDrawableLayer(TransitionDrawable transitionDrawable, int layer,
int targetWidth, int targetHeight) {
Drawable drawable = transitionDrawable.getDrawable(layer);
@@ -1227,8 +1291,10 @@ public class MediaControlPanel {
PackageManager packageManager = mContext.getPackageManager();
// Set up media source app's logo.
Drawable icon = packageManager.getApplicationIcon(applicationInfo);
- ImageView headerLogoImageView = mRecommendationViewHolder.getCardIcon();
- headerLogoImageView.setImageDrawable(icon);
+ if (!mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
+ ImageView headerLogoImageView = mRecommendationViewHolder.getCardIcon();
+ headerLogoImageView.setImageDrawable(icon);
+ }
fetchAndUpdateRecommendationColors(icon);
// Set up media rec card's tap action if applicable.
@@ -1248,7 +1314,15 @@ public class MediaControlPanel {
// Set up media item cover.
ImageView mediaCoverImageView = mediaCoverItems.get(itemIndex);
- mediaCoverImageView.setImageIcon(recommendation.getIcon());
+ if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
+ bindRecommendationArtwork(
+ recommendation,
+ data.getPackageName(),
+ itemIndex
+ );
+ } else {
+ mediaCoverImageView.setImageIcon(recommendation.getIcon());
+ }
// Set up the media item's click listener if applicable.
ViewGroup mediaCoverContainer = mediaCoverContainers.get(itemIndex);
@@ -1278,7 +1352,6 @@ public class MediaControlPanel {
recommendation.getTitle(), artistName, appName));
}
-
// Set up title
CharSequence title = recommendation.getTitle();
hasTitle |= !TextUtils.isEmpty(title);
@@ -1356,6 +1429,10 @@ public class MediaControlPanel {
int textPrimaryColor = MediaColorSchemesKt.textPrimaryFromScheme(colorScheme);
int textSecondaryColor = MediaColorSchemesKt.textSecondaryFromScheme(colorScheme);
+ if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
+ mRecommendationViewHolder.getCardTitle().setTextColor(textPrimaryColor);
+ }
+
mRecommendationViewHolder.getRecommendations()
.setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
mRecommendationViewHolder.getMediaTitles().forEach(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 2ec7be6eaa32..1e6002cdc717 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -25,6 +25,7 @@ import com.android.systemui.media.controls.models.GutsViewHolder
import com.android.systemui.media.controls.models.player.MediaViewHolder
import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.calculateAlpha
+import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.animation.MeasurementOutput
import com.android.systemui.util.animation.TransitionLayout
@@ -45,7 +46,8 @@ constructor(
private val context: Context,
private val configurationController: ConfigurationController,
private val mediaHostStatesManager: MediaHostStatesManager,
- private val logger: MediaViewLogger
+ private val logger: MediaViewLogger,
+ private val mediaFlags: MediaFlags,
) {
/**
@@ -646,8 +648,13 @@ constructor(
expandedLayout.load(context, R.xml.media_session_expanded)
}
TYPE.RECOMMENDATION -> {
- collapsedLayout.load(context, R.xml.media_recommendation_collapsed)
- expandedLayout.load(context, R.xml.media_recommendation_expanded)
+ if (mediaFlags.isRecommendationCardUpdateEnabled()) {
+ collapsedLayout.load(context, R.xml.media_recommendations_view_collapsed)
+ expandedLayout.load(context, R.xml.media_recommendations_view_expanded)
+ } else {
+ collapsedLayout.load(context, R.xml.media_recommendation_collapsed)
+ expandedLayout.load(context, R.xml.media_recommendation_expanded)
+ }
}
}
refreshState()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index ab03930e42ac..81efa3688aab 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -51,4 +51,8 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) {
* whether the underlying notification was dismissed
*/
fun isRetainingPlayersEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_SESSIONS)
+
+ /** Check whether we show the updated recommendation card. */
+ fun isRecommendationCardUpdateEnabled() =
+ featureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 1edb837f3ca9..e665d832a568 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -34,8 +34,8 @@ import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnail
import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.shared.system.ActivityManagerWrapper
+import com.android.systemui.mediaprojection.devicepolicy.MediaProjectionDevicePolicyModule
+import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
import com.android.systemui.statusbar.policy.ConfigurationController
import dagger.Binds
@@ -54,13 +54,12 @@ import kotlinx.coroutines.SupervisorJob
@Qualifier @Retention(AnnotationRetention.BINARY) annotation class HostUserHandle
-@Qualifier @Retention(AnnotationRetention.BINARY) annotation class PersonalProfile
-
-@Qualifier @Retention(AnnotationRetention.BINARY) annotation class WorkProfile
-
@Retention(AnnotationRetention.RUNTIME) @Scope annotation class MediaProjectionAppSelectorScope
-@Module(subcomponents = [MediaProjectionAppSelectorComponent::class])
+@Module(
+ subcomponents = [MediaProjectionAppSelectorComponent::class],
+ includes = [MediaProjectionDevicePolicyModule::class]
+)
interface MediaProjectionModule {
@Binds
@IntoMap
@@ -110,20 +109,6 @@ interface MediaProjectionAppSelectorModule {
): ConfigurationController = ConfigurationControllerImpl(activity)
@Provides
- @PersonalProfile
- @MediaProjectionAppSelectorScope
- fun personalUserHandle(activityManagerWrapper: ActivityManagerWrapper): UserHandle {
- // Current foreground user is the 'personal' profile
- return UserHandle.of(activityManagerWrapper.currentUserId)
- }
-
- @Provides
- @WorkProfile
- @MediaProjectionAppSelectorScope
- fun workProfileUserHandle(userTracker: UserTracker): UserHandle? =
- userTracker.userProfiles.find { it.isManagedProfile }?.userHandle
-
- @Provides
@HostUserHandle
@MediaProjectionAppSelectorScope
fun hostUserHandle(activity: MediaProjectionAppSelectorActivity): UserHandle {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt
new file mode 100644
index 000000000000..829b0ddbe3a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.mediaprojection.appselector
+
+import android.content.Context
+import android.os.UserHandle
+import com.android.internal.R as AndroidR
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider
+import com.android.internal.app.ResolverListAdapter
+import com.android.systemui.R
+import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
+import javax.inject.Inject
+
+@MediaProjectionAppSelectorScope
+class MediaProjectionBlockerEmptyStateProvider
+@Inject
+constructor(
+ @HostUserHandle private val hostAppHandle: UserHandle,
+ @PersonalProfile private val personalProfileHandle: UserHandle,
+ private val policyResolver: ScreenCaptureDevicePolicyResolver,
+ private val context: Context
+) : EmptyStateProvider {
+
+ override fun getEmptyState(resolverListAdapter: ResolverListAdapter): EmptyState? {
+ val screenCaptureAllowed =
+ policyResolver.isScreenCaptureAllowed(
+ targetAppUserHandle = resolverListAdapter.userHandle,
+ hostAppUserHandle = hostAppHandle
+ )
+
+ val isHostAppInPersonalProfile = hostAppHandle == personalProfileHandle
+
+ val subtitle =
+ if (isHostAppInPersonalProfile) {
+ AndroidR.string.resolver_cant_share_with_personal_apps_explanation
+ } else {
+ AndroidR.string.resolver_cant_share_with_work_apps_explanation
+ }
+
+ if (!screenCaptureAllowed) {
+ return object : EmptyState {
+ override fun getSubtitle(): String = context.resources.getString(subtitle)
+ override fun getTitle(): String =
+ context.resources.getString(
+ R.string.screen_capturing_disabled_by_policy_dialog_title
+ )
+ override fun onEmptyStateShown() {
+ // TODO(b/237397740) report analytics
+ }
+ override fun shouldSkipDataRebuild(): Boolean = true
+ }
+ }
+ return null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/MediaProjectionDevicePolicyModule.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/MediaProjectionDevicePolicyModule.kt
new file mode 100644
index 000000000000..13b71a727dd9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/MediaProjectionDevicePolicyModule.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.mediaprojection.devicepolicy
+
+import android.os.UserHandle
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.system.ActivityManagerWrapper
+import dagger.Module
+import dagger.Provides
+import javax.inject.Qualifier
+
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class WorkProfile
+
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class PersonalProfile
+
+/** Module for media projection device policy related dependencies */
+@Module
+class MediaProjectionDevicePolicyModule {
+ @Provides
+ @PersonalProfile
+ fun personalUserHandle(activityManagerWrapper: ActivityManagerWrapper): UserHandle {
+ // Current foreground user is the 'personal' profile
+ return UserHandle.of(activityManagerWrapper.currentUserId)
+ }
+
+ @Provides
+ @WorkProfile
+ fun workProfileUserHandle(userTracker: UserTracker): UserHandle? =
+ userTracker.userProfiles.find { it.isManagedProfile }?.userHandle
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolver.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolver.kt
new file mode 100644
index 000000000000..6bd33e7e5c97
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolver.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.mediaprojection.devicepolicy
+
+import android.app.admin.DevicePolicyManager
+import android.os.UserHandle
+import android.os.UserManager
+import javax.inject.Inject
+
+/**
+ * Utility class to resolve if screen capture allowed for a particular target app/host app pair. It
+ * caches the state of the policies, so you need to create a new instance of this class if you want
+ * to react to updated policies state.
+ */
+class ScreenCaptureDevicePolicyResolver
+@Inject
+constructor(
+ private val devicePolicyManager: DevicePolicyManager,
+ private val userManager: UserManager,
+ @PersonalProfile private val personalProfileUserHandle: UserHandle,
+ @WorkProfile private val workProfileUserHandle: UserHandle?
+) {
+
+ /**
+ * Returns true if [hostAppUserHandle] is allowed to perform screen capture of
+ * [targetAppUserHandle]
+ */
+ fun isScreenCaptureAllowed(
+ targetAppUserHandle: UserHandle,
+ hostAppUserHandle: UserHandle,
+ ): Boolean {
+ if (hostAppUserHandle.isWorkProfile() && workProfileScreenCaptureDisabled) {
+ // Disable screen capturing as host apps should not capture the screen
+ return false
+ }
+
+ if (!hostAppUserHandle.isWorkProfile() && personalProfileScreenCaptureDisabled) {
+ // Disable screen capturing as personal apps should not capture the screen
+ return false
+ }
+
+ if (targetAppUserHandle.isWorkProfile()) {
+ // Work profile target
+ if (workProfileScreenCaptureDisabled) {
+ // Do not allow sharing work profile apps as work profile capturing is disabled
+ return false
+ }
+ } else {
+ // Personal profile target
+ if (hostAppUserHandle.isWorkProfile() && disallowSharingIntoManagedProfile) {
+ // Do not allow sharing of personal apps into work profile apps
+ return false
+ }
+
+ if (personalProfileScreenCaptureDisabled) {
+ // Disable screen capturing as personal apps should not be captured
+ return false
+ }
+ }
+
+ return true
+ }
+
+ /**
+ * Returns true if [hostAppUserHandle] is NOT allowed to capture an app from any profile,
+ * could be useful to finish the screen capture flow as soon as possible when the screen
+ * could not be captured at all.
+ */
+ fun isScreenCaptureCompletelyDisabled(hostAppUserHandle: UserHandle): Boolean {
+ val isWorkAppsCaptureDisabled =
+ if (workProfileUserHandle != null) {
+ !isScreenCaptureAllowed(
+ targetAppUserHandle = workProfileUserHandle,
+ hostAppUserHandle = hostAppUserHandle
+ )
+ } else true
+
+ val isPersonalAppsCaptureDisabled =
+ !isScreenCaptureAllowed(
+ targetAppUserHandle = personalProfileUserHandle,
+ hostAppUserHandle = hostAppUserHandle
+ )
+
+ return isWorkAppsCaptureDisabled && isPersonalAppsCaptureDisabled
+ }
+
+ private val personalProfileScreenCaptureDisabled: Boolean by lazy {
+ devicePolicyManager.getScreenCaptureDisabled(
+ /* admin */ null,
+ personalProfileUserHandle.identifier
+ )
+ }
+
+ private val workProfileScreenCaptureDisabled: Boolean by lazy {
+ workProfileUserHandle?.let {
+ devicePolicyManager.getScreenCaptureDisabled(/* admin */ null, it.identifier)
+ }
+ ?: false
+ }
+
+ private val disallowSharingIntoManagedProfile: Boolean by lazy {
+ workProfileUserHandle?.let {
+ userManager.hasUserRestrictionForUser(
+ UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE,
+ it
+ )
+ }
+ ?: false
+ }
+
+ private fun UserHandle?.isWorkProfile(): Boolean = this == workProfileUserHandle
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt
new file mode 100644
index 000000000000..a6b3da04ad80
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.mediaprojection.devicepolicy
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+/** Dialog that shows that screen capture is disabled on this device. */
+class ScreenCaptureDisabledDialog(context: Context) : SystemUIDialog(context) {
+
+ init {
+ setTitle(context.getString(R.string.screen_capturing_disabled_by_policy_dialog_title))
+ setMessage(
+ context.getString(R.string.screen_capturing_disabled_by_policy_dialog_description)
+ )
+ setIcon(R.drawable.ic_cast)
+ setButton(BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ -> cancel() }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index 2a6ca1acb38e..8ad2f867a073 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -17,11 +17,11 @@ package com.android.systemui.privacy
import android.content.Context
import android.util.AttributeSet
import android.view.ViewGroup
-import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import com.android.settingslib.Utils
import com.android.systemui.R
+import com.android.systemui.animation.LaunchableFrameLayout
import com.android.systemui.statusbar.events.BackgroundAnimatableView
class OngoingPrivacyChip @JvmOverloads constructor(
@@ -29,7 +29,7 @@ class OngoingPrivacyChip @JvmOverloads constructor(
attrs: AttributeSet? = null,
defStyleAttrs: Int = 0,
defStyleRes: Int = 0
-) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
+) : LaunchableFrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
private var iconMargin = 0
private var iconSize = 0
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 100853caa2d7..98af9dfe7f37 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -314,7 +314,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
if (!TILES_SETTING.equals(key)) {
return;
}
- Log.d(TAG, "Recreating tiles");
if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
}
@@ -327,6 +326,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
}
}
if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
+ Log.d(TAG, "Recreating tiles: " + tileSpecs);
mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
tile -> {
Log.d(TAG, "Destroying tile: " + tile.getKey());
@@ -372,6 +372,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
Log.d(TAG, "Destroying not available tile: " + tileSpec);
mQSLogger.logTileDestroyed(tileSpec, "Tile not available");
}
+ } else {
+ Log.d(TAG, "No factory for a spec: " + tileSpec);
}
} catch (Throwable t) {
Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java
index 87c12c25a0a5..6577cf696b61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java
@@ -20,10 +20,21 @@ import android.content.Context;
import android.util.AttributeSet;
import android.widget.Button;
+import com.android.systemui.animation.LaunchableView;
+import com.android.systemui.animation.LaunchableViewDelegate;
+
+import kotlin.Unit;
+
/**
* A Button which doesn't have overlapping drawing commands
*/
-public class AlphaOptimizedButton extends Button {
+public class AlphaOptimizedButton extends Button implements LaunchableView {
+ private LaunchableViewDelegate mDelegate = new LaunchableViewDelegate(this,
+ (visibility) -> {
+ super.setVisibility(visibility);
+ return Unit.INSTANCE;
+ });
+
public AlphaOptimizedButton(Context context) {
super(context);
}
@@ -45,4 +56,14 @@ public class AlphaOptimizedButton extends Button {
public boolean hasOverlappingRendering() {
return false;
}
+
+ @Override
+ public void setShouldBlockVisibilityChanges(boolean block) {
+ mDelegate.setShouldBlockVisibilityChanges(block);
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ mDelegate.setVisibility(visibility);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
index 2c8677dee4d9..2d80edb8d2f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
@@ -19,14 +19,14 @@ package com.android.systemui.statusbar.phone.userswitcher
import android.content.Context
import android.util.AttributeSet
import android.widget.ImageView
-import android.widget.LinearLayout
import android.widget.TextView
import com.android.systemui.R
+import com.android.systemui.common.ui.view.LaunchableLinearLayout
class StatusBarUserSwitcherContainer(
context: Context?,
attrs: AttributeSet?
-) : LinearLayout(context, attrs) {
+) : LaunchableLinearLayout(context, attrs) {
lateinit var text: TextView
private set
lateinit var avatar: ImageView
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
index 08ee0af17fb0..56c5d3b433ff 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
@@ -27,6 +27,8 @@ import android.view.ViewTreeObserver
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.animation.LaunchableView
+import com.android.systemui.animation.LaunchableViewDelegate
import com.android.systemui.statusbar.CrossFadeHelper
/**
@@ -38,7 +40,7 @@ class TransitionLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
-) : ConstraintLayout(context, attrs, defStyleAttr) {
+) : ConstraintLayout(context, attrs, defStyleAttr), LaunchableView {
private val boundsRect = Rect()
private val originalGoneChildrenSet: MutableSet<Int> = mutableSetOf()
@@ -50,7 +52,11 @@ class TransitionLayout @JvmOverloads constructor(
private var desiredMeasureWidth = 0
private var desiredMeasureHeight = 0
- private var transitionVisibility = View.VISIBLE
+ private val delegate =
+ LaunchableViewDelegate(
+ this,
+ superSetVisibility = { super.setVisibility(it) },
+ )
/**
* The measured state of this view which is the one we will lay ourselves out with. This
@@ -83,11 +89,12 @@ class TransitionLayout @JvmOverloads constructor(
}
}
- override fun setTransitionVisibility(visibility: Int) {
- // We store the last transition visibility assigned to this view to restore it later if
- // necessary.
- super.setTransitionVisibility(visibility)
- transitionVisibility = visibility
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {
+ delegate.setShouldBlockVisibilityChanges(block)
+ }
+
+ override fun setVisibility(visibility: Int) {
+ delegate.setVisibility(visibility)
}
override fun onFinishInflate() {
@@ -173,14 +180,6 @@ class TransitionLayout @JvmOverloads constructor(
translationY = currentState.translation.y
CrossFadeHelper.fadeIn(this, currentState.alpha)
-
- // CrossFadeHelper#fadeIn will change this view visibility, which overrides the transition
- // visibility. We set the transition visibility again to make sure that this view plays well
- // with GhostView, which sets the transition visibility and is used for activity launch
- // animations.
- if (transitionVisibility != View.VISIBLE) {
- setTransitionVisibility(transitionVisibility)
- }
}
private fun applyCurrentStateOnPredraw() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
index a61cd23b60fc..578e1d4d02ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
@@ -15,6 +15,7 @@ import android.view.RemoteAnimationAdapter
import android.view.RemoteAnimationTarget
import android.view.SurfaceControl
import android.view.ViewGroup
+import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -26,6 +27,7 @@ import junit.framework.Assert.assertTrue
import junit.framework.AssertionFailedError
import kotlin.concurrent.thread
import org.junit.After
+import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -195,6 +197,13 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
verify(controller).onLaunchAnimationStart(anyBoolean())
}
+ @Test
+ fun creatingControllerFromNormalViewThrows() {
+ assertThrows(IllegalArgumentException::class.java) {
+ ActivityLaunchAnimator.Controller.fromView(FrameLayout(mContext))
+ }
+ }
+
private fun fakeWindow(): RemoteAnimationTarget {
val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */)
val taskInfo = ActivityManager.RunningTaskInfo()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index cac4a0e5432c..1e62fd2332c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -26,6 +26,7 @@ import junit.framework.Assert.assertNull
import junit.framework.Assert.assertTrue
import org.junit.After
import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -260,6 +261,13 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
assertThat(touchSurface.visibility).isEqualTo(View.GONE)
}
+ @Test
+ fun creatingControllerFromNormalViewThrows() {
+ assertThrows(IllegalArgumentException::class.java) {
+ DialogLaunchAnimator.Controller.fromView(FrameLayout(mContext))
+ }
+ }
+
private fun createAndShowDialog(
animator: DialogLaunchAnimator = dialogLaunchAnimator,
): TestDialog {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
index 3696ec540baf..0798d73cc2f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
@@ -16,58 +16,34 @@
package com.android.systemui.animation
-import android.graphics.drawable.Drawable
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewParent
+import android.widget.FrameLayout
import androidx.test.filters.SmallTest
-import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.SysuiTestCase
-import org.junit.Before
+import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class GhostedViewLaunchAnimatorControllerTest : SysuiTestCase() {
- @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
- @Mock lateinit var view: View
- @Mock lateinit var rootView: ViewGroup
- @Mock lateinit var viewParent: ViewParent
- @Mock lateinit var drawable: Drawable
- lateinit var controller: GhostedViewLaunchAnimatorController
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- whenever(view.rootView).thenReturn(rootView)
- whenever(view.background).thenReturn(drawable)
- whenever(view.height).thenReturn(0)
- whenever(view.width).thenReturn(0)
- whenever(view.parent).thenReturn(viewParent)
- whenever(view.visibility).thenReturn(View.VISIBLE)
- whenever(view.invalidate()).then { /* NO-OP */ }
- whenever(view.getLocationOnScreen(any())).then { /* NO-OP */ }
- whenever(interactionJankMonitor.begin(any(), anyInt())).thenReturn(true)
- whenever(interactionJankMonitor.end(anyInt())).thenReturn(true)
- controller = GhostedViewLaunchAnimatorController(view, 0, interactionJankMonitor)
- }
-
@Test
fun animatingOrphanViewDoesNotCrash() {
val state = LaunchAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
+ val controller = GhostedViewLaunchAnimatorController(LaunchableFrameLayout(mContext))
controller.onIntentStarted(willAnimate = true)
controller.onLaunchAnimationStart(isExpandingFullyAbove = true)
controller.onLaunchAnimationProgress(state, progress = 0f, linearProgress = 0f)
controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
}
+
+ @Test
+ fun creatingControllerFromNormalViewThrows() {
+ assertThrows(IllegalArgumentException::class.java) {
+ GhostedViewLaunchAnimatorController(FrameLayout(mContext))
+ }
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index 89c728082cc5..a4cf15c3fafa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -31,13 +31,13 @@ import android.content.ComponentName;
import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.view.View;
-import android.widget.ImageView;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.common.ui.view.LaunchableImageView;
import com.android.systemui.controls.ControlsServiceInfo;
import com.android.systemui.controls.controller.ControlsController;
import com.android.systemui.controls.controller.StructureInfo;
@@ -90,7 +90,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase {
private View mView;
@Mock
- private ImageView mHomeControlsView;
+ private LaunchableImageView mHomeControlsView;
@Mock
private ActivityStarter mActivityStarter;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index e4e95e580a7c..5e5dc8b20c65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -33,6 +33,7 @@ import com.android.systemui.media.controls.models.recommendation.SmartspaceMedia
import com.android.systemui.media.controls.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.media.controls.ui.MediaHierarchyManager.Companion.LOCATION_QS
+import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -87,6 +88,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
@Mock lateinit var smartspaceMediaData: SmartspaceMediaData
@Mock lateinit var mediaCarousel: MediaScrollView
@Mock lateinit var pageIndicator: PageIndicator
+ @Mock lateinit var mediaFlags: MediaFlags
@Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
@Captor
lateinit var configListener: ArgumentCaptor<ConfigurationController.ConfigurationListener>
@@ -114,7 +116,8 @@ class MediaCarouselControllerTest : SysuiTestCase() {
falsingManager,
dumpManager,
logger,
- debugLogger
+ debugLogger,
+ mediaFlags,
)
verify(configurationController).addCallback(capture(configListener))
verify(mediaDataManager).addListener(capture(listener))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index b35dd266e422..ce22b19b3721 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -204,6 +204,9 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Mock private lateinit var coverContainer1: ViewGroup
@Mock private lateinit var coverContainer2: ViewGroup
@Mock private lateinit var coverContainer3: ViewGroup
+ @Mock private lateinit var recAppIconItem: CachingIconView
+ @Mock private lateinit var recCardTitle: TextView
+ @Mock private lateinit var coverItem: ImageView
private lateinit var coverItem1: ImageView
private lateinit var coverItem2: ImageView
private lateinit var coverItem3: ImageView
@@ -220,6 +223,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
this.set(Flags.UMO_TURBULENCE_NOISE, false)
this.set(Flags.MEDIA_FALSING_PENALTY, true)
this.set(Flags.MEDIA_EXPLICIT_INDICATOR, true)
+ this.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, false)
}
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -2059,6 +2063,51 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
@Test
+ fun bindRecommendation_setAfterExecutors() {
+ fakeFeatureFlag.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, true)
+ whenever(recommendationViewHolder.mediaAppIcons)
+ .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
+ whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
+ whenever(recommendationViewHolder.mediaCoverItems)
+ .thenReturn(listOf(coverItem, coverItem, coverItem))
+
+ val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(bmp)
+ canvas.drawColor(Color.RED)
+ val albumArt = Icon.createWithBitmap(bmp)
+ val data =
+ smartspaceData.copy(
+ recommendations =
+ listOf(
+ SmartspaceAction.Builder("id1", "title1")
+ .setSubtitle("subtitle1")
+ .setIcon(albumArt)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id2", "title2")
+ .setSubtitle("subtitle1")
+ .setIcon(albumArt)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id3", "title3")
+ .setSubtitle("subtitle1")
+ .setIcon(albumArt)
+ .setExtras(Bundle.EMPTY)
+ .build()
+ )
+ )
+
+ player.attachRecommendation(recommendationViewHolder)
+ player.bindRecommendation(data)
+ bgExecutor.runAllReady()
+ mainExecutor.runAllReady()
+
+ verify(recCardTitle).setTextColor(any<Int>())
+ verify(recAppIconItem, times(3)).setImageDrawable(any(Drawable::class.java))
+ verify(coverItem, times(3)).setImageDrawable(any(Drawable::class.java))
+ }
+
+ @Test
fun onButtonClick_touchRippleFlagEnabled_playsTouchRipple() {
fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true)
val semanticActions =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index 4ed6d7cf6bd0..2f7eac2ad4ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -22,6 +22,7 @@ import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.util.animation.MeasurementInput
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.animation.TransitionViewState
@@ -55,6 +56,7 @@ class MediaViewControllerTest : SysuiTestCase() {
@Mock private lateinit var mediaTitleWidgetState: WidgetState
@Mock private lateinit var mediaSubTitleWidgetState: WidgetState
@Mock private lateinit var mediaContainerWidgetState: WidgetState
+ @Mock private lateinit var mediaFlags: MediaFlags
val delta = 0.1F
@@ -64,7 +66,13 @@ class MediaViewControllerTest : SysuiTestCase() {
fun setup() {
MockitoAnnotations.initMocks(this)
mediaViewController =
- MediaViewController(context, configurationController, mediaHostStatesManager, logger)
+ MediaViewController(
+ context,
+ configurationController,
+ mediaHostStatesManager,
+ logger,
+ mediaFlags,
+ )
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt
new file mode 100644
index 000000000000..e8b6f9bd3478
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt
@@ -0,0 +1,703 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.mediaprojection.devicepolicy
+
+import android.app.admin.DevicePolicyManager
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.ArgumentMatchers.any
+
+abstract class BaseScreenCaptureDevicePolicyResolverTest(private val precondition: Preconditions) :
+ SysuiTestCase() {
+
+ abstract class Preconditions(
+ val personalScreenCaptureDisabled: Boolean,
+ val workScreenCaptureDisabled: Boolean,
+ val disallowShareIntoManagedProfile: Boolean
+ )
+
+ protected val devicePolicyManager: DevicePolicyManager = mock()
+ protected val userManager: UserManager = mock()
+
+ protected val personalUserHandle: UserHandle = UserHandle.of(123)
+ protected val workUserHandle: UserHandle = UserHandle.of(456)
+
+ protected val policyResolver =
+ ScreenCaptureDevicePolicyResolver(
+ devicePolicyManager,
+ userManager,
+ personalUserHandle,
+ workUserHandle
+ )
+
+ @Before
+ fun setUp() {
+ setUpPolicies()
+ }
+
+ private fun setUpPolicies() {
+ whenever(
+ devicePolicyManager.getScreenCaptureDisabled(
+ any(),
+ eq(personalUserHandle.identifier)
+ )
+ )
+ .thenReturn(precondition.personalScreenCaptureDisabled)
+
+ whenever(devicePolicyManager.getScreenCaptureDisabled(any(), eq(workUserHandle.identifier)))
+ .thenReturn(precondition.workScreenCaptureDisabled)
+
+ whenever(
+ userManager.hasUserRestrictionForUser(
+ eq(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE),
+ eq(workUserHandle)
+ )
+ )
+ .thenReturn(precondition.disallowShareIntoManagedProfile)
+ }
+}
+
+@RunWith(Parameterized::class)
+@SmallTest
+class IsAllowedScreenCaptureDevicePolicyResolverTest(
+ private val test: IsScreenCaptureAllowedTestCase
+) : BaseScreenCaptureDevicePolicyResolverTest(test.given) {
+
+ companion object {
+ @Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams() =
+ listOf(
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false,
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true,
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false,
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true,
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ )
+ }
+
+ class Preconditions(
+ personalScreenCaptureDisabled: Boolean,
+ workScreenCaptureDisabled: Boolean,
+ disallowShareIntoManagedProfile: Boolean,
+ val isHostInWorkProfile: Boolean,
+ val isTargetInWorkProfile: Boolean,
+ ) :
+ BaseScreenCaptureDevicePolicyResolverTest.Preconditions(
+ personalScreenCaptureDisabled,
+ workScreenCaptureDisabled,
+ disallowShareIntoManagedProfile
+ )
+
+ data class IsScreenCaptureAllowedTestCase(
+ val given: Preconditions,
+ val expectedScreenCaptureAllowed: Boolean
+ ) {
+ override fun toString(): String =
+ "isScreenCaptureAllowed: " +
+ "host[${if (given.isHostInWorkProfile) "work" else "personal"} profile], " +
+ "target[${if (given.isTargetInWorkProfile) "work" else "personal"} profile], " +
+ "personal screen capture disabled = ${given.personalScreenCaptureDisabled}, " +
+ "work screen capture disabled = ${given.workScreenCaptureDisabled}, " +
+ "disallow share into managed profile = ${given.disallowShareIntoManagedProfile}, " +
+ "expected screen capture allowed = $expectedScreenCaptureAllowed"
+ }
+
+ @Test
+ fun test() {
+ val targetAppUserHandle =
+ if (test.given.isTargetInWorkProfile) workUserHandle else personalUserHandle
+ val hostAppUserHandle =
+ if (test.given.isHostInWorkProfile) workUserHandle else personalUserHandle
+
+ val screenCaptureAllowed =
+ policyResolver.isScreenCaptureAllowed(targetAppUserHandle, hostAppUserHandle)
+
+ assertWithMessage("Screen capture policy resolved incorrectly")
+ .that(screenCaptureAllowed)
+ .isEqualTo(test.expectedScreenCaptureAllowed)
+ }
+}
+
+@RunWith(Parameterized::class)
+@SmallTest
+class IsCompletelyNotAllowedScreenCaptureDevicePolicyResolverTest(
+ private val test: IsScreenCaptureCompletelyDisabledTestCase
+) : BaseScreenCaptureDevicePolicyResolverTest(test.given) {
+
+ companion object {
+ @Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams() =
+ listOf(
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureCompletelyDisabled = false,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureCompletelyDisabled = false,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureCompletelyDisabled = false,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureCompletelyDisabled = false,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureCompletelyDisabled = true,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureCompletelyDisabled = true,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureCompletelyDisabled = true,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureCompletelyDisabled = true,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureCompletelyDisabled = false,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureCompletelyDisabled = false,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureCompletelyDisabled = true,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureCompletelyDisabled = true,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureCompletelyDisabled = false,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureCompletelyDisabled = false,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureCompletelyDisabled = true,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureCompletelyDisabled = true,
+ )
+ )
+ }
+
+ class Preconditions(
+ personalScreenCaptureDisabled: Boolean,
+ workScreenCaptureDisabled: Boolean,
+ disallowShareIntoManagedProfile: Boolean,
+ val isHostInWorkProfile: Boolean,
+ ) :
+ BaseScreenCaptureDevicePolicyResolverTest.Preconditions(
+ personalScreenCaptureDisabled,
+ workScreenCaptureDisabled,
+ disallowShareIntoManagedProfile
+ )
+
+ data class IsScreenCaptureCompletelyDisabledTestCase(
+ val given: Preconditions,
+ val expectedScreenCaptureCompletelyDisabled: Boolean
+ ) {
+ override fun toString(): String =
+ "isScreenCaptureCompletelyDisabled: " +
+ "host[${if (given.isHostInWorkProfile) "work" else "personal"} profile], " +
+ "personal screen capture disabled = ${given.personalScreenCaptureDisabled}, " +
+ "work screen capture disabled = ${given.workScreenCaptureDisabled}, " +
+ "disallow share into managed profile = ${given.disallowShareIntoManagedProfile}, " +
+ "expected screen capture completely disabled = $expectedScreenCaptureCompletelyDisabled"
+ }
+
+ @Test
+ fun test() {
+ val hostAppUserHandle =
+ if (test.given.isHostInWorkProfile) workUserHandle else personalUserHandle
+
+ val completelyDisabled = policyResolver.isScreenCaptureCompletelyDisabled(hostAppUserHandle)
+
+ assertWithMessage("Screen capture policy resolved incorrectly")
+ .that(completelyDisabled)
+ .isEqualTo(test.expectedScreenCaptureCompletelyDisabled)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index a7733a29f242..d6dfc8509165 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -35,6 +35,7 @@ import android.view.View
import com.android.internal.logging.MetricsLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.LaunchableFrameLayout
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.qs.QSTile
@@ -342,7 +343,7 @@ class CustomTileTest : SysuiTestCase() {
val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
tile.qsTile.activityLaunchForClick = pi
- tile.handleClick(mock(View::class.java))
+ tile.handleClick(mock(LaunchableFrameLayout::class.java))
testableLooper.processAllMessages()
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 10d50c29f42f..d9c38a12f458 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -32,6 +32,7 @@ import static android.app.ActivityManager.INSTR_FLAG_DISABLE_TEST_API_CHECKS;
import static android.app.ActivityManager.INSTR_FLAG_NO_RESTART;
import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY;
import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
@@ -3341,6 +3342,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
mBatteryStatsService.noteProcessDied(app.info.uid, pid);
+ mOomAdjuster.updateShortFgsOwner(app.info.uid, pid, false);
if (!app.isKilled()) {
if (!fromBinderDied) {
@@ -18305,6 +18307,23 @@ public class ActivityManagerService extends IActivityManager.Stub
public void unregisterStrictModeCallback(int callingPid) {
mStrictModeCallbacks.remove(callingPid);
}
+
+ @Override
+ public boolean canHoldWakeLocksInDeepDoze(int uid, int procstate) {
+ // This method is called with the PowerManager lock held. Do not hold AM here.
+
+ // If the procstate is high enough, it's always allowed.
+ if (procstate <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+ return true;
+ }
+ // IF it's too low, it's not allowed.
+ if (procstate > PROCESS_STATE_IMPORTANT_FOREGROUND) {
+ return false;
+ }
+ // If it's PROCESS_STATE_IMPORTANT_FOREGROUND, then we allow it only wheen the UID
+ // has a SHORT_FGS.
+ return mOomAdjuster.hasUidShortForegroundService(uid);
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 114e2c139794..e02dda642b40 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -126,6 +126,7 @@ import android.os.Trace;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
+import android.util.SparseSetArray;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.CompositeRWLock;
@@ -364,6 +365,19 @@ public class OomAdjuster {
@GuardedBy("mService")
private boolean mPendingFullOomAdjUpdate = false;
+ /**
+ * PIDs that has a SHORT_SERVICE. We need to access it with the PowerManager lock held,
+ * so we use a fine-grained lock here.
+ */
+ @GuardedBy("mPidsWithShortFgs")
+ private final ArraySet<Integer> mPidsWithShortFgs = new ArraySet<>();
+
+ /**
+ * UIDs -> PIDs map, used with mPidsWithShortFgs.
+ */
+ @GuardedBy("mPidsWithShortFgs")
+ private final SparseSetArray<Integer> mUidsToPidsWithShortFgs = new SparseSetArray<>();
+
/** Overrideable by a test */
@VisibleForTesting
protected boolean isChangeEnabled(@CachedCompatChangeId int cachedCompatChangeId,
@@ -1849,6 +1863,11 @@ public class OomAdjuster {
int capabilityFromFGS = 0; // capability from foreground service.
+ final boolean hasForegroundServices = psr.hasForegroundServices();
+ final boolean hasNonShortForegroundServices = psr.hasNonShortForegroundServices();
+ final boolean hasShortForegroundServices = hasForegroundServices
+ && !psr.areAllShortForegroundServicesProcstateTimedOut(now);
+
// Adjust for FGS or "has-overlay-ui".
if (adj > PERCEPTIBLE_APP_ADJ
|| procState > PROCESS_STATE_FOREGROUND_SERVICE) {
@@ -1856,7 +1875,7 @@ public class OomAdjuster {
int newAdj = 0;
int newProcState = 0;
- if (psr.hasForegroundServices() && psr.hasNonShortForegroundServices()) {
+ if (hasForegroundServices && hasNonShortForegroundServices) {
// For regular (non-short) FGS.
adjType = "fg-service";
newAdj = PERCEPTIBLE_APP_ADJ;
@@ -1867,11 +1886,11 @@ public class OomAdjuster {
newAdj = PERCEPTIBLE_APP_ADJ;
newProcState = PROCESS_STATE_IMPORTANT_FOREGROUND;
- } else if (psr.hasForegroundServices()) {
+ } else if (hasForegroundServices) {
// If we get here, hasNonShortForegroundServices() must be false.
// TODO(short-service): Proactively run OomAjudster when the grace period finish.
- if (psr.areAllShortForegroundServicesProcstateTimedOut(now)) {
+ if (!hasShortForegroundServices) {
// All the short-FGSes within this process are timed out. Don't promote to FGS.
// TODO(short-service): Should we set some unique oom-adj to make it detectable,
// in a long trace?
@@ -1907,6 +1926,7 @@ public class OomAdjuster {
}
}
}
+ updateShortFgsOwner(psr.mApp.uid, psr.mApp.mPid, hasShortForegroundServices);
// If the app was recently in the foreground and moved to a foreground service status,
// allow it to get a higher rank in memory for some time, compared to other foreground
@@ -3337,4 +3357,40 @@ public class OomAdjuster {
mCachedAppOptimizer.unfreezeAppLSP(app, oomAdjReason);
}
}
+
+ /**
+ * Update {@link #mPidsWithShortFgs} and {@link #mUidsToPidsWithShortFgs} to keep track
+ * of which UID/PID has a short FGS.
+ *
+ * TODO(short-FGS): Remove it and all the relevant code once SHORT_FGS use the FGS procstate.
+ */
+ void updateShortFgsOwner(int uid, int pid, boolean add) {
+ synchronized (mPidsWithShortFgs) {
+ if (add) {
+ mUidsToPidsWithShortFgs.add(uid, pid);
+ mPidsWithShortFgs.add(pid);
+ } else {
+ mUidsToPidsWithShortFgs.remove(uid, pid);
+ mPidsWithShortFgs.remove(pid);
+ }
+ }
+ }
+
+ /**
+ * Whether a UID has a (non-timed-out) short FGS or not.
+ * It's indirectly called by PowerManager, so we can't hold the AM lock in it.
+ */
+ boolean hasUidShortForegroundService(int uid) {
+ synchronized (mPidsWithShortFgs) {
+ final ArraySet<Integer> pids = mUidsToPidsWithShortFgs.get(uid);
+ if (pids == null || pids.size() == 0) {
+ return false;
+ }
+ for (int i = pids.size() - 1; i >= 0; i--) {
+ final int pid = pids.valueAt(i);
+ return mPidsWithShortFgs.contains(pid);
+ }
+ }
+ return false;
+ }
}
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 7839ada9d5b2..7af7ed5fff65 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -37,8 +37,12 @@ import android.util.Pair;
import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -68,11 +72,14 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback,
private String[] mMethodNames = {"getDevicesForAttributes"};
private static final boolean USE_CACHE_FOR_GETDEVICES = true;
+ private static final Object sDeviceCacheLock = new Object();
private ConcurrentHashMap<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
mLastDevicesForAttr = new ConcurrentHashMap<>();
+ @GuardedBy("sDeviceCacheLock")
private ConcurrentHashMap<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
mDevicesForAttrCache;
- private final Object mDeviceCacheLock = new Object();
+ @GuardedBy("sDeviceCacheLock")
+ private long mDevicesForAttributesCacheClearTimeMs = System.currentTimeMillis();
private int[] mMethodCacheHit;
/**
* Map that stores all attributes + forVolume pairs that are registered for
@@ -249,9 +256,11 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback,
AudioSystem.setRoutingCallback(sSingletonDefaultAdapter);
AudioSystem.setVolumeRangeInitRequestCallback(sSingletonDefaultAdapter);
if (USE_CACHE_FOR_GETDEVICES) {
- sSingletonDefaultAdapter.mDevicesForAttrCache =
- new ConcurrentHashMap<>(AudioSystem.getNumStreamTypes());
- sSingletonDefaultAdapter.mMethodCacheHit = new int[NB_MEASUREMENTS];
+ synchronized (sDeviceCacheLock) {
+ sSingletonDefaultAdapter.mDevicesForAttrCache =
+ new ConcurrentHashMap<>(AudioSystem.getNumStreamTypes());
+ sSingletonDefaultAdapter.mMethodCacheHit = new int[NB_MEASUREMENTS];
+ }
}
if (ENABLE_GETDEVICES_STATS) {
sSingletonDefaultAdapter.mMethodCallCounter = new int[NB_MEASUREMENTS];
@@ -265,8 +274,9 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback,
if (DEBUG_CACHE) {
Log.d(TAG, "---- clearing cache ----------");
}
- synchronized (mDeviceCacheLock) {
+ synchronized (sDeviceCacheLock) {
if (mDevicesForAttrCache != null) {
+ mDevicesForAttributesCacheClearTimeMs = System.currentTimeMillis();
// Save latest cache to determine routing updates
mLastDevicesForAttr.putAll(mDevicesForAttrCache);
@@ -298,7 +308,7 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback,
if (USE_CACHE_FOR_GETDEVICES) {
ArrayList<AudioDeviceAttributes> res;
final Pair<AudioAttributes, Boolean> key = new Pair(attributes, forVolume);
- synchronized (mDeviceCacheLock) {
+ synchronized (sDeviceCacheLock) {
res = mDevicesForAttrCache.get(key);
if (res == null) {
res = AudioSystem.getDevicesForAttributes(attributes, forVolume);
@@ -656,23 +666,31 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback,
*/
public void dump(PrintWriter pw) {
pw.println("\nAudioSystemAdapter:");
- pw.println(" mDevicesForAttrCache:");
- if (mDevicesForAttrCache != null) {
- for (Map.Entry<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
- entry : mDevicesForAttrCache.entrySet()) {
- final AudioAttributes attributes = entry.getKey().first;
- try {
- final int stream = attributes.getVolumeControlStream();
- pw.println("\t" + attributes + " forVolume: " + entry.getKey().second
- + " stream: "
- + AudioSystem.STREAM_NAMES[stream] + "(" + stream + ")");
- for (AudioDeviceAttributes devAttr : entry.getValue()) {
- pw.println("\t\t" + devAttr);
+ final DateTimeFormatter formatter = DateTimeFormatter
+ .ofPattern("MM-dd HH:mm:ss:SSS")
+ .withLocale(Locale.US)
+ .withZone(ZoneId.systemDefault());
+ synchronized (sDeviceCacheLock) {
+ pw.println(" last cache clear time: " + formatter.format(
+ Instant.ofEpochMilli(mDevicesForAttributesCacheClearTimeMs)));
+ pw.println(" mDevicesForAttrCache:");
+ if (mDevicesForAttrCache != null) {
+ for (Map.Entry<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
+ entry : mDevicesForAttrCache.entrySet()) {
+ final AudioAttributes attributes = entry.getKey().first;
+ try {
+ final int stream = attributes.getVolumeControlStream();
+ pw.println("\t" + attributes + " forVolume: " + entry.getKey().second
+ + " stream: "
+ + AudioSystem.STREAM_NAMES[stream] + "(" + stream + ")");
+ for (AudioDeviceAttributes devAttr : entry.getValue()) {
+ pw.println("\t\t" + devAttr);
+ }
+ } catch (IllegalArgumentException e) {
+ // dump could fail if attributes do not map to a stream.
+ pw.println("\t dump failed for attributes: " + attributes);
+ Log.e(TAG, "dump failed", e);
}
- } catch (IllegalArgumentException e) {
- // dump could fail if attributes do not map to a stream.
- pw.println("\t dump failed for attributes: " + attributes);
- Log.e(TAG, "dump failed", e);
}
}
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 05464c810061..592daa61f36c 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -402,7 +402,6 @@ class AutomaticBrightnessController {
boolean userChangedAutoBrightnessAdjustment, int displayPolicy,
boolean shouldResetShortTermModel) {
mState = state;
- mHbmController.setAutoBrightnessEnabled(mState);
// While dozing, the application processor may be suspended which will prevent us from
// receiving new information from the light sensor. On some devices, we may be able to
// switch to a wake-up light sensor instead but for now we will simply disable the sensor
@@ -466,7 +465,6 @@ class AutomaticBrightnessController {
mHandler.sendEmptyMessage(MSG_RUN_UPDATE);
}
- @VisibleForTesting
float getAmbientLux() {
return mAmbientLux;
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index fc2a4e509bf5..abb0ff625ef3 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -719,7 +719,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
@Override
- public void setBrightnessToFollow(float leadDisplayBrightness, float nits) {
+ public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux) {
+ mHbmController.onAmbientLuxChange(ambientLux);
if (mAutomaticBrightnessController == null || nits < 0) {
mBrightnessToFollow = leadDisplayBrightness;
} else {
@@ -751,7 +752,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
for (int i = 0; i < followers.size(); i++) {
DisplayPowerControllerInterface follower = followers.valueAt(i);
- follower.setBrightnessToFollow(PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1);
+ follower.setBrightnessToFollow(PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
+ /* ambientLux= */ 0);
}
}
@@ -1523,6 +1525,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mShouldResetShortTermModel);
mShouldResetShortTermModel = false;
}
+ mHbmController.setAutoBrightnessEnabled(mUseAutoBrightness
+ ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
+ : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
if (mBrightnessTracker != null) {
mBrightnessTracker.setBrightnessConfiguration(mBrightnessConfiguration);
@@ -1634,9 +1639,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mAppliedThrottling = false;
}
+ float ambientLux = mAutomaticBrightnessController == null ? 0
+ : mAutomaticBrightnessController.getAmbientLux();
for (int i = 0; i < displayBrightnessFollowers.size(); i++) {
DisplayPowerControllerInterface follower = displayBrightnessFollowers.valueAt(i);
- follower.setBrightnessToFollow(rawBrightnessState, convertToNits(rawBrightnessState));
+ follower.setBrightnessToFollow(rawBrightnessState, convertToNits(rawBrightnessState),
+ ambientLux);
}
if (updateScreenBrightnessSetting) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index a7444151510f..2e91bdb66072 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -1236,6 +1236,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mShouldResetShortTermModel);
mShouldResetShortTermModel = false;
}
+ mHbmController.setAutoBrightnessEnabled(mUseAutoBrightness
+ ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
+ : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
if (mBrightnessTracker != null) {
mBrightnessTracker.setBrightnessConfiguration(mBrightnessConfiguration);
@@ -1347,9 +1350,12 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mAppliedThrottling = false;
}
+ float ambientLux = mAutomaticBrightnessController == null ? 0
+ : mAutomaticBrightnessController.getAmbientLux();
for (int i = 0; i < displayBrightnessFollowers.size(); i++) {
DisplayPowerControllerInterface follower = displayBrightnessFollowers.valueAt(i);
- follower.setBrightnessToFollow(rawBrightnessState, convertToNits(rawBrightnessState));
+ follower.setBrightnessToFollow(rawBrightnessState, convertToNits(rawBrightnessState),
+ ambientLux);
}
if (updateScreenBrightnessSetting) {
@@ -2138,7 +2144,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
}
@Override
- public void setBrightnessToFollow(float leadDisplayBrightness, float nits) {
+ public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux) {
+ mHbmController.onAmbientLuxChange(ambientLux);
if (mAutomaticBrightnessController == null || nits < 0) {
mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness);
} else {
@@ -2219,7 +2226,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
}
for (int i = 0; i < followers.size(); i++) {
DisplayPowerControllerInterface follower = followers.valueAt(i);
- follower.setBrightnessToFollow(PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1);
+ follower.setBrightnessToFollow(PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
+ /* ambientLux= */ 0);
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index a95ac74b27c8..4612ec9cf2dc 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -173,9 +173,11 @@ public interface DisplayPowerControllerInterface {
* displays.
* @param leadDisplayBrightness The brightness of the lead display in the set of concurrent
* displays
- * @param nits The brightness value in nits if the device supports nits
+ * @param nits The brightness value in nits if the device supports nits. Set to a negative
+ * number otherwise.
+ * @param ambientLux The lux value that will be passed to {@link HighBrightnessModeController}
*/
- void setBrightnessToFollow(float leadDisplayBrightness, float nits);
+ void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux);
/**
* Add an additional display that will copy the brightness value from this display. This is used
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index c29ab09fa386..e8cb4e207629 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -40,6 +40,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.SynchronousUserSwitchObserver;
import android.content.BroadcastReceiver;
@@ -311,6 +312,7 @@ public final class PowerManagerService extends SystemService
private SettingsObserver mSettingsObserver;
private DreamManagerInternal mDreamManager;
private LogicalLight mAttentionLight;
+ private ActivityManagerInternal mAmInternal;
private final InattentiveSleepWarningController mInattentiveSleepWarningOverlayController;
private final AmbientDisplaySuppressionController mAmbientDisplaySuppressionController;
@@ -1237,6 +1239,7 @@ public final class PowerManagerService extends SystemService
mDisplayManagerInternal = getLocalService(DisplayManagerInternal.class);
mPolicy = getLocalService(WindowManagerPolicy.class);
mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class);
+ mAmInternal = getLocalService(ActivityManagerInternal.class);
mAttentionDetector.systemReady(mContext);
SensorManager sensorManager = new SystemSensorManager(mContext, mHandler.getLooper());
@@ -4077,9 +4080,8 @@ public final class PowerManagerService extends SystemService
final UidState state = wakeLock.mUidState;
if (Arrays.binarySearch(mDeviceIdleWhitelist, appid) < 0 &&
Arrays.binarySearch(mDeviceIdleTempWhitelist, appid) < 0 &&
- state.mProcState != ActivityManager.PROCESS_STATE_NONEXISTENT &&
- state.mProcState >
- ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+ (mAmInternal != null && !mAmInternal.canHoldWakeLocksInDeepDoze(
+ state.mUid, state.mProcState))) {
disabled = true;
}
}
diff --git a/services/core/java/com/android/server/wm/SurfaceSyncGroupController.java b/services/core/java/com/android/server/wm/SurfaceSyncGroupController.java
index 75691caad246..1711845247f9 100644
--- a/services/core/java/com/android/server/wm/SurfaceSyncGroupController.java
+++ b/services/core/java/com/android/server/wm/SurfaceSyncGroupController.java
@@ -63,7 +63,7 @@ class SurfaceSyncGroupController {
if (callback == null) {
return false;
}
- outAddToSyncGroupResult.mParentSyncGroup = root;
+ outAddToSyncGroupResult.mParentSyncGroup = root.mISurfaceSyncGroup;
outAddToSyncGroupResult.mTransactionReadyCallback = callback;
return true;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 7281fafc93f2..ed78e720df73 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -30,7 +30,9 @@ import static com.android.server.job.JobSchedulerService.RARE_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -95,6 +97,8 @@ public class JobSchedulerServiceTest {
private ActivityManagerInternal mActivityMangerInternal;
@Mock
private Context mContext;
+ @Mock
+ private PackageManagerInternal mPackageManagerInternal;
private class TestJobSchedulerService extends JobSchedulerService {
TestJobSchedulerService(Context context) {
@@ -121,6 +125,8 @@ public class JobSchedulerServiceTest {
.when(() -> LocalServices.getService(AppStandbyInternal.class));
doReturn(mock(BatteryManagerInternal.class))
.when(() -> LocalServices.getService(BatteryManagerInternal.class));
+ doReturn(mPackageManagerInternal)
+ .when(() -> LocalServices.getService(PackageManagerInternal.class));
doReturn(mock(UsageStatsManagerInternal.class))
.when(() -> LocalServices.getService(UsageStatsManagerInternal.class));
when(mContext.getString(anyInt())).thenReturn("some_test_string");
@@ -138,9 +144,6 @@ public class JobSchedulerServiceTest {
// Used in JobConcurrencyManager.
doReturn(mock(UserManagerInternal.class))
.when(() -> LocalServices.getService(UserManagerInternal.class));
- // Used in JobStatus.
- doReturn(mock(PackageManagerInternal.class))
- .when(() -> LocalServices.getService(PackageManagerInternal.class));
// Called via IdleController constructor.
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
when(mContext.getResources()).thenReturn(mock(Resources.class));
@@ -197,8 +200,13 @@ public class JobSchedulerServiceTest {
private JobStatus createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder,
int callingUid) {
+ return createJobStatus(testTag, jobInfoBuilder, callingUid, "com.android.test");
+ }
+
+ private JobStatus createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder,
+ int callingUid, String sourcePkg) {
return JobStatus.createFromJobInfo(
- jobInfoBuilder.build(), callingUid, "com.android.test", 0, "JSSTest", testTag);
+ jobInfoBuilder.build(), callingUid, sourcePkg, 0, "JSSTest", testTag);
}
private void grantRunLongJobsPermission(boolean grant) {
@@ -1238,4 +1246,57 @@ public class JobSchedulerServiceTest {
0, "JSSTest", ""));
}
}
+
+ /** Tests that jobs are removed from the pending list if the user stops the app. */
+ @Test
+ public void testUserStopRemovesPending() {
+ spyOn(mService);
+
+ JobStatus job1a = createJobStatus("testUserStopRemovesPending",
+ createJobInfo(1), 1, "pkg1");
+ JobStatus job1b = createJobStatus("testUserStopRemovesPending",
+ createJobInfo(2), 1, "pkg1");
+ JobStatus job2a = createJobStatus("testUserStopRemovesPending",
+ createJobInfo(1), 2, "pkg2");
+ JobStatus job2b = createJobStatus("testUserStopRemovesPending",
+ createJobInfo(2), 2, "pkg2");
+ doReturn(1).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 0);
+ doReturn(11).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 1);
+ doReturn(2).when(mPackageManagerInternal).getPackageUid("pkg2", 0, 0);
+
+ mService.getPendingJobQueue().clear();
+ mService.getPendingJobQueue().add(job1a);
+ mService.getPendingJobQueue().add(job1b);
+ mService.getPendingJobQueue().add(job2a);
+ mService.getPendingJobQueue().add(job2b);
+ mService.getJobStore().add(job1a);
+ mService.getJobStore().add(job1b);
+ mService.getJobStore().add(job2a);
+ mService.getJobStore().add(job2b);
+
+ mService.stopUserVisibleJobsInternal("pkg1", 1);
+ assertEquals(4, mService.getPendingJobQueue().size());
+ assertTrue(mService.getPendingJobQueue().contains(job1a));
+ assertTrue(mService.getPendingJobQueue().contains(job1b));
+ assertTrue(mService.getPendingJobQueue().contains(job2a));
+ assertTrue(mService.getPendingJobQueue().contains(job2b));
+
+ mService.stopUserVisibleJobsInternal("pkg1", 0);
+ assertEquals(2, mService.getPendingJobQueue().size());
+ assertFalse(mService.getPendingJobQueue().contains(job1a));
+ assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job1a));
+ assertFalse(mService.getPendingJobQueue().contains(job1b));
+ assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job1b));
+ assertTrue(mService.getPendingJobQueue().contains(job2a));
+ assertTrue(mService.getPendingJobQueue().contains(job2b));
+
+ mService.stopUserVisibleJobsInternal("pkg2", 0);
+ assertEquals(0, mService.getPendingJobQueue().size());
+ assertFalse(mService.getPendingJobQueue().contains(job1a));
+ assertFalse(mService.getPendingJobQueue().contains(job1b));
+ assertFalse(mService.getPendingJobQueue().contains(job2a));
+ assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job2a));
+ assertFalse(mService.getPendingJobQueue().contains(job2b));
+ assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job2b));
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index de54537bbabe..9b48114fafec 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -201,7 +201,7 @@ public class WallpaperManagerServiceTests {
public void setUp() {
MockitoAnnotations.initMocks(this);
- ExtendedMockito.doAnswer(invocation -> {
+ ExtendedMockito.doAnswer(invocation -> {
int userId = (invocation.getArgument(0));
return getWallpaperTestDir(userId);
}).when(() -> WallpaperUtils.getWallpaperDir(anyInt()));
@@ -315,7 +315,8 @@ public class WallpaperManagerServiceTests {
spyOn(mService.mWallpaperDisplayHelper);
doReturn(true).when(mService.mWallpaperDisplayHelper)
- .isUsableDisplay(any(Display.class), mService.mLastWallpaper.connection.mClientUid);
+ .isUsableDisplay(any(Display.class),
+ eq(mService.mLastWallpaper.connection.mClientUid));
mService.mLastWallpaper.connection.attachEngine(mock(IWallpaperEngine.class),
DEFAULT_DISPLAY);
diff --git a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
index 8cbed2c7ffb8..4412cfe4a3b5 100644
--- a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
@@ -305,4 +305,60 @@ public class OomAdjusterTests {
assertEquals("Interaction event time was not updated correctly.",
interactionEventTime, mProcessRecord.mState.getInteractionEventTime());
}
+
+ private void updateShortFgsOwner(int uid, int pid, boolean add) {
+ sService.mOomAdjuster.updateShortFgsOwner(uid, pid, add);
+ }
+
+ private void assertHasUidShortForegroundService(int uid, boolean expected) {
+ assertEquals(expected, sService.mOomAdjuster.hasUidShortForegroundService(uid));
+ }
+
+ @Test
+ public void testHasUidShortForegroundService() {
+ assertHasUidShortForegroundService(1, false);
+ assertHasUidShortForegroundService(2, false);
+ assertHasUidShortForegroundService(3, false);
+ assertHasUidShortForegroundService(100, false);
+ assertHasUidShortForegroundService(101, false);
+
+ updateShortFgsOwner(1, 100, true);
+ assertHasUidShortForegroundService(1, true);
+ assertHasUidShortForegroundService(100, false);
+ assertHasUidShortForegroundService(2, false);
+
+ updateShortFgsOwner(1, 101, true);
+ assertHasUidShortForegroundService(1, true);
+ assertHasUidShortForegroundService(101, false);
+ assertHasUidShortForegroundService(2, false);
+
+ updateShortFgsOwner(2, 200, true);
+ assertHasUidShortForegroundService(1, true);
+ assertHasUidShortForegroundService(2, true);
+ assertHasUidShortForegroundService(200, false);
+
+ updateShortFgsOwner(1, 101, false);
+ assertHasUidShortForegroundService(1, true);
+ assertHasUidShortForegroundService(2, true);
+
+ updateShortFgsOwner(1, 99, false); // unused PID
+ assertHasUidShortForegroundService(1, true);
+ assertHasUidShortForegroundService(2, true);
+
+ updateShortFgsOwner(1, 100, false);
+ assertHasUidShortForegroundService(1, false);
+ assertHasUidShortForegroundService(2, true);
+
+ updateShortFgsOwner(1, 100, true);
+ assertHasUidShortForegroundService(1, true);
+ assertHasUidShortForegroundService(2, true);
+
+ updateShortFgsOwner(2, 200, false);
+ assertHasUidShortForegroundService(1, true);
+ assertHasUidShortForegroundService(2, false);
+
+ updateShortFgsOwner(2, 201, true);
+ assertHasUidShortForegroundService(1, true);
+ assertHasUidShortForegroundService(2, true);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index a0fb3deeb131..d71deaf3a88d 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -16,6 +16,7 @@
package com.android.server.power;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
import static android.app.AppOpsManager.MODE_ALLOWED;
@@ -29,6 +30,8 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalMatchers.gt;
+import static org.mockito.AdditionalMatchers.leq;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -150,6 +153,9 @@ public class PowerManagerServiceTest {
@Mock
private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
+ @Mock
+ private ActivityManagerInternal mActivityManagerInternal;
+
private PowerManagerService mService;
private ContextWrapper mContextSpy;
private BatteryReceiver mBatteryReceiver;
@@ -205,6 +211,7 @@ public class PowerManagerServiceTest {
addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
addLocalServiceMock(AttentionManagerInternal.class, mAttentionManagerInternalMock);
addLocalServiceMock(DreamManagerInternal.class, mDreamManagerInternalMock);
+ addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternal);
mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
mResourcesSpy = spy(mContextSpy.getResources());
@@ -221,6 +228,14 @@ public class PowerManagerServiceTest {
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
+
+ // Set up canHoldWakeLocksInDeepDoze.
+ // - procstate <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE -> true
+ // - procstate > PROCESS_STATE_BOUND_FOREGROUND_SERVICE -> false
+ when(mActivityManagerInternal.canHoldWakeLocksInDeepDoze(
+ anyInt(), leq(PROCESS_STATE_BOUND_FOREGROUND_SERVICE))).thenReturn(true);
+ when(mActivityManagerInternal.canHoldWakeLocksInDeepDoze(
+ anyInt(), gt(PROCESS_STATE_BOUND_FOREGROUND_SERVICE))).thenReturn(false);
}
private PowerManagerService createService() {
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index b51feb3ac5c7..593ee4a7fa1a 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -90,9 +90,6 @@
android:foregroundServiceType="mediaProjection"
android:enabled="true">
</service>
-
- <service android:name="com.android.server.wm.scvh.EmbeddedSCVHService"
- android:process="com.android.server.wm.scvh.embedded_process" />
</application>
<instrumentation
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
index f655242afac8..22d72ed3d361 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
@@ -55,7 +55,7 @@ public class SurfaceSyncGroupTest {
SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
SyncTarget syncTarget = new SyncTarget();
- syncGroup.addToSync(syncTarget, false /* parentSyncGroupMerge */);
+ syncGroup.add(syncTarget, null /* runnable */);
syncGroup.markSyncReady();
syncTarget.onBufferReady();
@@ -73,9 +73,9 @@ public class SurfaceSyncGroupTest {
SyncTarget syncTarget2 = new SyncTarget();
SyncTarget syncTarget3 = new SyncTarget();
- syncGroup.addToSync(syncTarget1, false /* parentSyncGroupMerge */);
- syncGroup.addToSync(syncTarget2, false /* parentSyncGroupMerge */);
- syncGroup.addToSync(syncTarget3, false /* parentSyncGroupMerge */);
+ syncGroup.add(syncTarget1, null /* runnable */);
+ syncGroup.add(syncTarget2, null /* runnable */);
+ syncGroup.add(syncTarget3, null /* runnable */);
syncGroup.markSyncReady();
syncTarget1.onBufferReady();
@@ -97,11 +97,11 @@ public class SurfaceSyncGroupTest {
SyncTarget syncTarget1 = new SyncTarget();
SyncTarget syncTarget2 = new SyncTarget();
- assertTrue(syncGroup.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup.add(syncTarget1, null /* runnable */));
syncGroup.markSyncReady();
// Adding to a sync that has been completed is also invalid since the sync id has been
// cleared.
- assertFalse(syncGroup.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+ assertFalse(syncGroup.add(syncTarget2, null /* runnable */));
}
@Test
@@ -117,8 +117,8 @@ public class SurfaceSyncGroupTest {
SyncTarget syncTarget1 = new SyncTarget();
SyncTarget syncTarget2 = new SyncTarget();
- assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
- assertTrue(syncGroup2.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
+ assertTrue(syncGroup2.add(syncTarget2, null /* runnable */));
syncGroup1.markSyncReady();
syncGroup2.markSyncReady();
@@ -147,10 +147,10 @@ public class SurfaceSyncGroupTest {
SyncTarget syncTarget1 = new SyncTarget();
SyncTarget syncTarget2 = new SyncTarget();
- assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
- assertTrue(syncGroup2.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
+ assertTrue(syncGroup2.add(syncTarget2, null /* runnable */));
syncGroup1.markSyncReady();
- syncGroup2.addToSync(syncGroup1, false /* parentSyncGroupMerge */);
+ syncGroup2.add(syncGroup1, null /* runnable */);
syncGroup2.markSyncReady();
// Finish syncTarget2 first to test that the syncGroup is not complete until the merged sync
@@ -183,8 +183,8 @@ public class SurfaceSyncGroupTest {
SyncTarget syncTarget1 = new SyncTarget();
SyncTarget syncTarget2 = new SyncTarget();
- assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
- assertTrue(syncGroup2.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
+ assertTrue(syncGroup2.add(syncTarget2, null /* runnable */));
syncGroup1.markSyncReady();
syncTarget1.onBufferReady();
@@ -192,7 +192,7 @@ public class SurfaceSyncGroupTest {
finishedLatch1.await(5, TimeUnit.SECONDS);
assertEquals(0, finishedLatch1.getCount());
- syncGroup2.addToSync(syncGroup1, false /* parentSyncGroupMerge */);
+ syncGroup2.add(syncGroup1, null /* runnable */);
syncGroup2.markSyncReady();
syncTarget2.onBufferReady();
@@ -216,12 +216,12 @@ public class SurfaceSyncGroupTest {
SyncTarget syncTarget2 = new SyncTarget();
SyncTarget syncTarget3 = new SyncTarget();
- assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
- assertTrue(syncGroup1.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
+ assertTrue(syncGroup1.add(syncTarget2, null /* runnable */));
// Add syncTarget1 to syncGroup2 so it forces syncGroup1 into syncGroup2
- assertTrue(syncGroup2.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
- assertTrue(syncGroup2.addToSync(syncTarget3, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup2.add(syncTarget1, null /* runnable */));
+ assertTrue(syncGroup2.add(syncTarget3, null /* runnable */));
syncGroup1.markSyncReady();
syncGroup2.markSyncReady();
@@ -261,13 +261,13 @@ public class SurfaceSyncGroupTest {
SyncTarget syncTarget2 = new SyncTarget();
SyncTarget syncTarget3 = new SyncTarget();
- assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
- assertTrue(syncGroup1.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
+ assertTrue(syncGroup1.add(syncTarget2, null /* runnable */));
syncTarget2.onBufferReady();
// Add syncTarget1 to syncGroup2 so it forces syncGroup1 into syncGroup2
- assertTrue(syncGroup2.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
- assertTrue(syncGroup2.addToSync(syncTarget3, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup2.add(syncTarget1, null /* runnable */));
+ assertTrue(syncGroup2.add(syncTarget3, null /* runnable */));
syncGroup1.markSyncReady();
syncGroup2.markSyncReady();
@@ -303,7 +303,9 @@ public class SurfaceSyncGroupTest {
SurfaceSyncGroup.setTransactionFactory(() -> targetTransaction);
SyncTarget syncTarget = new SyncTarget();
- assertTrue(syncGroup.addToSync(syncTarget, true /* parentSyncGroupMerge */));
+ assertTrue(
+ syncGroup.add(syncTarget.mISurfaceSyncGroup, true /* parentSyncGroupMerge */,
+ null /* runnable */));
syncTarget.markSyncReady();
// When parentSyncGroupMerge is true, the transaction passed in merges the main SyncGroup
@@ -328,7 +330,7 @@ public class SurfaceSyncGroupTest {
SurfaceSyncGroup.setTransactionFactory(() -> targetTransaction);
SyncTarget syncTarget = new SyncTarget();
- assertTrue(syncGroup.addToSync(syncTarget, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup.add(syncTarget, null /* runnable */));
syncTarget.markSyncReady();
// When parentSyncGroupMerge is false, the transaction passed in should not merge
@@ -343,9 +345,9 @@ public class SurfaceSyncGroupTest {
SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
SyncTarget syncTarget = new SyncTarget();
- syncGroup.addToSync(syncTarget, false /* parentSyncGroupMerge */);
+ syncGroup.add(syncTarget, null /* runnable */);
// Add the syncTarget to the same syncGroup and ensure it doesn't crash.
- syncGroup.addToSync(syncTarget, false /* parentSyncGroupMerge */);
+ syncGroup.add(syncTarget, null /* runnable */);
syncGroup.markSyncReady();
syncTarget.onBufferReady();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupContinuousTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java
index eef7cc25eee7..ad7314c9bc60 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupContinuousTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,15 +22,10 @@ import static android.server.wm.WindowManagerState.getLogicalDisplaySize;
import android.app.KeyguardManager;
import android.os.PowerManager;
-import android.platform.test.annotations.Presubmit;
-import android.view.SurfaceControl;
import android.view.cts.surfacevalidator.CapturedActivity;
-import android.window.SurfaceSyncGroup;
import androidx.test.rule.ActivityTestRule;
-import com.android.server.wm.scvh.SyncValidatorSCVHTestCase;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -38,7 +33,7 @@ import org.junit.rules.TestName;
import java.util.Objects;
-public class SurfaceSyncGroupContinuousTest {
+public class SurfaceViewSyncContinuousTest {
@Rule
public TestName mName = new TestName();
@@ -59,33 +54,10 @@ public class SurfaceSyncGroupContinuousTest {
pressWakeupButton();
pressUnlockButton();
}
- SurfaceSyncGroup.setTransactionFactory(SurfaceControl.Transaction::new);
}
@Test
public void testSurfaceViewSyncDuringResize() throws Throwable {
- mCapturedActivity.verifyTest(new SurfaceSyncGroupValidatorTestCase(), mName);
- }
-
- @Test
- public void testSurfaceControlViewHostIPCSync_Fast() throws Throwable {
- mCapturedActivity.verifyTest(
- new SyncValidatorSCVHTestCase(0 /* delayMs */, false /* overrideDefaultDuration */),
- mName);
- }
-
- @Test
- public void testSurfaceControlViewHostIPCSync_Slow() throws Throwable {
- mCapturedActivity.verifyTest(new SyncValidatorSCVHTestCase(100 /* delayMs */,
- false /* overrideDefaultDuration */), mName);
- }
-
- @Test
- @Presubmit
- public void testSurfaceControlViewHostIPCSync_Short() throws Throwable {
- mCapturedActivity.setMinimumCaptureDurationMs(5000);
- mCapturedActivity.verifyTest(
- new SyncValidatorSCVHTestCase(0 /* delayMs */, true /* overrideDefaultDuration */),
- mName);
+ mCapturedActivity.verifyTest(new SurfaceViewSyncValidatorTestCase(), mName);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupValidatorTestCase.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncValidatorTestCase.java
index 1fa0c2300173..93c1badd5e78 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupValidatorTestCase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncValidatorTestCase.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,7 +41,7 @@ import androidx.annotation.NonNull;
* never an empty area (black color). The test uses {@link SurfaceSyncGroup} class to gather the
* content it wants to synchronize.
*/
-public class SurfaceSyncGroupValidatorTestCase implements ISurfaceValidatorTestCase {
+public class SurfaceViewSyncValidatorTestCase implements ISurfaceValidatorTestCase {
private static final String TAG = "SurfaceSyncGroupValidatorTestCase";
private final Runnable mRunnable = new Runnable() {
@@ -76,7 +76,7 @@ public class SurfaceSyncGroupValidatorTestCase implements ISurfaceValidatorTestC
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
int height) {
if (mSyncGroup != null) {
- mSyncGroup.addToSync(mSurfaceView, frameCallback ->
+ mSyncGroup.add(mSurfaceView, frameCallback ->
mRenderingThread.setFrameCallback(frameCallback));
mSyncGroup.markSyncReady();
mSyncGroup = null;
@@ -133,7 +133,7 @@ public class SurfaceSyncGroupValidatorTestCase implements ISurfaceValidatorTestC
mRenderingThread.pauseRendering();
mSyncGroup = new SurfaceSyncGroup(TAG);
- mSyncGroup.addToSync(mParent.getRootSurfaceControl());
+ mSyncGroup.add(mParent.getRootSurfaceControl(), null /* runanble */);
ViewGroup.LayoutParams svParams = mSurfaceView.getLayoutParams();
svParams.height = height;
diff --git a/services/tests/wmtests/src/com/android/server/wm/scvh/EmbeddedSCVHService.java b/services/tests/wmtests/src/com/android/server/wm/scvh/EmbeddedSCVHService.java
deleted file mode 100644
index 3bd577cb1d64..000000000000
--- a/services/tests/wmtests/src/com/android/server/wm/scvh/EmbeddedSCVHService.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.scvh;
-
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
-
-import android.annotation.Nullable;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.hardware.display.DisplayManager;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.view.Display;
-import android.view.SurfaceControl.Transaction;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-
-public class EmbeddedSCVHService extends Service {
- private static final String TAG = "SCVHEmbeddedService";
- private SurfaceControlViewHost mVr;
-
- private Handler mHandler;
-
- @Override
- public void onCreate() {
- super.onCreate();
- mHandler = new Handler(Looper.getMainLooper());
- }
-
- @Nullable
- @Override
- public IBinder onBind(Intent intent) {
- // Return the interface
- return new AttachEmbeddedWindow();
- }
-
- public static class SlowView extends View {
- private long mDelayMs;
- public SlowView(Context context) {
- super(context);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- try {
- Thread.sleep(mDelayMs);
- } catch (InterruptedException e) {
- }
- }
-
- public void setDelay(long delayMs) {
- mDelayMs = delayMs;
- }
- }
-
- private class AttachEmbeddedWindow extends IAttachEmbeddedWindow.Stub {
- @Override
- public void attachEmbedded(IBinder hostToken, int width,
- int height, int displayId, long delayMs, IAttachEmbeddedWindowCallback callback) {
- mHandler.post(() -> {
- Context context = EmbeddedSCVHService.this;
- Display display = getApplicationContext().getSystemService(
- DisplayManager.class).getDisplay(displayId);
- mVr = new SurfaceControlViewHost(context, display, hostToken);
- FrameLayout content = new FrameLayout(context);
-
- SlowView slowView = new SlowView(context);
- slowView.setDelay(delayMs);
- slowView.setBackgroundColor(Color.BLUE);
- content.addView(slowView);
- WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height,
- TYPE_APPLICATION, 0, PixelFormat.OPAQUE);
- lp.setTitle("EmbeddedWindow");
- mVr.setView(content, lp);
-
- content.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(@NonNull View v) {
- // First frame isn't included in the sync so don't notify the host about the
- // surface package until the first draw has completed.
- Transaction transaction = new Transaction().addTransactionCommittedListener(
- getMainExecutor(), () -> {
- try {
- callback.onEmbeddedWindowAttached(mVr.getSurfacePackage());
- } catch (RemoteException e) {
- }
- });
- v.getRootSurfaceControl().applyTransactionOnDraw(transaction);
- }
-
- @Override
- public void onViewDetachedFromWindow(@NonNull View v) {
- }
- });
- });
- }
-
- @Override
- public void relayout(WindowManager.LayoutParams lp) {
- mHandler.post(() -> mVr.relayout(lp));
- }
- }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindow.aidl b/services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindow.aidl
deleted file mode 100644
index 343956759f09..000000000000
--- a/services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindow.aidl
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.scvh;
-
-import android.view.SurfaceControlViewHost.SurfacePackage;
-import android.os.IBinder;
-import com.android.server.wm.scvh.IAttachEmbeddedWindowCallback;
-import android.view.WindowManager.LayoutParams;
-
-interface IAttachEmbeddedWindow {
- void attachEmbedded(IBinder hostToken, int width, int height, int displayId, long delayMs, IAttachEmbeddedWindowCallback callback);
- void relayout(in LayoutParams lp);
-} \ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindowCallback.aidl b/services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindowCallback.aidl
deleted file mode 100644
index 92abfc8eea24..000000000000
--- a/services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindowCallback.aidl
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.scvh;
-
-import android.view.SurfaceControlViewHost.SurfacePackage;
-
-interface IAttachEmbeddedWindowCallback {
- void onEmbeddedWindowAttached(in SurfacePackage surfacePackage);
-} \ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/scvh/SyncValidatorSCVHTestCase.java b/services/tests/wmtests/src/com/android/server/wm/scvh/SyncValidatorSCVHTestCase.java
deleted file mode 100644
index 07dac8d7fcc3..000000000000
--- a/services/tests/wmtests/src/com/android/server/wm/scvh/SyncValidatorSCVHTestCase.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.scvh;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.SurfaceControlViewHost.SurfacePackage;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.cts.surfacevalidator.ISurfaceValidatorTestCase;
-import android.view.cts.surfacevalidator.PixelChecker;
-import android.widget.FrameLayout;
-import android.window.SurfaceSyncGroup;
-
-import androidx.annotation.NonNull;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-public class SyncValidatorSCVHTestCase implements ISurfaceValidatorTestCase {
- private static final String TAG = "SCVHSyncValidatorTestCase";
-
- private final Point[] mSizes = new Point[]{new Point(500, 500), new Point(700, 400),
- new Point(300, 800), new Point(200, 200)};
- private int mLastSizeIndex = 1;
-
- private final long mDelayMs;
- private final boolean mOverrideDefaultDuration;
-
- public SyncValidatorSCVHTestCase(long delayMs, boolean overrideDefaultDuration) {
- mDelayMs = delayMs;
- mOverrideDefaultDuration = overrideDefaultDuration;
- }
-
- private final Runnable mRunnable = new Runnable() {
- @Override
- public void run() {
- Point size = mSizes[mLastSizeIndex % mSizes.length];
- Runnable svResizeRunnable = () -> {
- ViewGroup.LayoutParams svParams = mSurfaceView.getLayoutParams();
- svParams.width = size.x;
- svParams.height = size.y;
- mSurfaceView.setLayoutParams(svParams);
- };
- Runnable resizeRunnable = () -> {
- try {
- final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(size.x,
- size.y,
- WindowManager.LayoutParams.TYPE_APPLICATION, 0,
- PixelFormat.TRANSPARENT);
- mIAttachEmbeddedWindow.relayout(lp);
- } catch (RemoteException e) {
- }
- };
-
- SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
- syncGroup.addToSync(mSurfaceView.getRootSurfaceControl(), svResizeRunnable);
- syncGroup.addToSync(mSurfacePackage, resizeRunnable);
- syncGroup.markSyncReady();
-
- mLastSizeIndex++;
-
- mHandler.postDelayed(this, mDelayMs + 50);
- }
- };
-
- private Handler mHandler;
- private SurfaceView mSurfaceView;
-
- private final CountDownLatch mReadyLatch = new CountDownLatch(1);
- private boolean mSurfaceCreated;
- private boolean mIsAttached;
- private final Object mLock = new Object();
- private int mDisplayId;
- private IAttachEmbeddedWindow mIAttachEmbeddedWindow;
- private SurfacePackage mSurfacePackage;
-
- final SurfaceHolder.Callback mCallback = new SurfaceHolder.Callback() {
- @Override
- public void surfaceCreated(@NonNull SurfaceHolder holder) {
- synchronized (mLock) {
- mSurfaceCreated = true;
- }
- if (isReadyToAttach()) {
- attachEmbedded();
- }
- }
-
- @Override
- public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
- int height) {
- }
-
- @Override
- public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
- }
- };
-
- @Override
- public PixelChecker getChecker() {
- return new PixelChecker(Color.BLACK) {
- @Override
- public boolean checkPixels(int matchingPixelCount, int width, int height) {
- // Content has been set up yet.
- if (mReadyLatch.getCount() > 0) {
- return true;
- }
- return matchingPixelCount == 0;
- }
- };
- }
-
- private final ServiceConnection mConnection = new ServiceConnection() {
- // Called when the connection with the service is established
- public void onServiceConnected(ComponentName className, IBinder service) {
- Log.d(TAG, "Service Connected");
- synchronized (mLock) {
- mIAttachEmbeddedWindow = IAttachEmbeddedWindow.Stub.asInterface(service);
- }
- if (isReadyToAttach()) {
- attachEmbedded();
- }
- }
-
- public void onServiceDisconnected(ComponentName className) {
- Log.d(TAG, "Service Disconnected");
- mIAttachEmbeddedWindow = null;
- synchronized (mLock) {
- mIsAttached = false;
- }
- }
- };
-
- private boolean isReadyToAttach() {
- synchronized (mLock) {
- if (!mSurfaceCreated) {
- Log.d(TAG, "surface is not created");
- }
- if (mIAttachEmbeddedWindow == null) {
- Log.d(TAG, "Service is not attached");
- }
- if (mIsAttached) {
- Log.d(TAG, "Already attached");
- }
-
- return mSurfaceCreated && mIAttachEmbeddedWindow != null && !mIsAttached;
- }
- }
-
- private void attachEmbedded() {
- synchronized (mLock) {
- mIsAttached = true;
- }
- try {
- mIAttachEmbeddedWindow.attachEmbedded(mSurfaceView.getHostToken(), mSizes[0].x,
- mSizes[0].y, mDisplayId, mDelayMs, new IAttachEmbeddedWindowCallback.Stub() {
- @Override
- public void onEmbeddedWindowAttached(SurfacePackage surfacePackage) {
- mHandler.post(() -> {
- mSurfacePackage = surfacePackage;
- mSurfaceView.setChildSurfacePackage(surfacePackage);
- mReadyLatch.countDown();
- });
- }
- });
- } catch (RemoteException e) {
- }
- }
-
- @Override
- public void start(Context context, FrameLayout parent) {
- mDisplayId = context.getDisplayId();
- mHandler = new Handler(Looper.getMainLooper());
-
- Intent intent = new Intent(context, EmbeddedSCVHService.class);
- intent.setAction(EmbeddedSCVHService.class.getName());
- context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
-
- mSurfaceView = new SurfaceView(context);
- mSurfaceView.getHolder().addCallback(mCallback);
-
- FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(mSizes[0].x,
- mSizes[0].y);
- layoutParams.gravity = Gravity.CENTER;
- parent.addView(mSurfaceView, layoutParams);
- }
-
- @Override
- public void waitForReady() {
-
- try {
- mReadyLatch.await(5, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- }
-
- assertEquals("Timed out waiting for setup", 0, mReadyLatch.getCount());
- assertNotNull("SurfacePackage is null", mSurfacePackage);
-
- mHandler.post(mRunnable);
- }
-
- @Override
- public void end() {
- mHandler.removeCallbacks(mRunnable);
- }
-
- @Override
- public boolean hasAnimation() {
- return !mOverrideDefaultDuration;
- }
-}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 83b909852d8f..0921834436bd 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -16355,6 +16355,7 @@ public class TelephonyManager {
/**
* Setup sIPhoneSubInfo for testing.
+ *
* @hide
*/
@VisibleForTesting
@@ -16365,9 +16366,21 @@ public class TelephonyManager {
}
/**
- * Whether device can connect to 5G network when two SIMs are active.
+ * Setup sISub for testing.
+ *
* @hide
- * TODO b/153669716: remove or make system API.
+ */
+ @VisibleForTesting
+ public static void setupISubForTest(ISub iSub) {
+ synchronized (sCacheLock) {
+ sISub = iSub;
+ }
+ }
+
+ /**
+ * Whether device can connect to 5G network when two SIMs are active.
+ *
+ * @hide TODO b/153669716: remove or make system API.
*/
public boolean canConnectTo5GInDsdsMode() {
ITelephony telephony = getITelephony();
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java
index ea727b91768d..359eb35384c7 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java
@@ -131,8 +131,8 @@ public class SurfaceControlViewHostSyncTest extends Activity implements SurfaceH
if (mSync) {
SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
- syncGroup.addToSync(getWindow().getRootSurfaceControl(), svResizeRunnable);
- syncGroup.addToSync(mSurfacePackage, resizeRunnable);
+ syncGroup.add(getWindow().getRootSurfaceControl(), svResizeRunnable);
+ syncGroup.add(mSurfacePackage, resizeRunnable);
syncGroup.markSyncReady();
} else {
svResizeRunnable.run();
diff --git a/tests/SurfaceViewSyncTest/src/com/android/test/SurfaceViewSyncActivity.java b/tests/SurfaceViewSyncTest/src/com/android/test/SurfaceViewSyncActivity.java
index d5983d064c80..d1f2112f649d 100644
--- a/tests/SurfaceViewSyncTest/src/com/android/test/SurfaceViewSyncActivity.java
+++ b/tests/SurfaceViewSyncTest/src/com/android/test/SurfaceViewSyncActivity.java
@@ -90,7 +90,7 @@ public class SurfaceViewSyncActivity extends Activity implements SurfaceHolder.C
if (mEnableSyncSwitch.isChecked()) {
mSyncGroup = new SurfaceSyncGroup(TAG);
- mSyncGroup.addToSync(container.getRootSurfaceControl());
+ mSyncGroup.add(container.getRootSurfaceControl(), null /* runnable */);
}
ViewGroup.LayoutParams svParams = mSurfaceView.getLayoutParams();
@@ -114,7 +114,7 @@ public class SurfaceViewSyncActivity extends Activity implements SurfaceHolder.C
mRenderingThread.renderFrame(null, width, height);
return;
}
- mSyncGroup.addToSync(mSurfaceView, frameCallback ->
+ mSyncGroup.add(mSurfaceView, frameCallback ->
mRenderingThread.renderFrame(frameCallback, width, height));
mSyncGroup.markSyncReady();
mSyncGroup = null;