diff options
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; |