diff options
278 files changed, 7532 insertions, 2555 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 8d2394b20438..44068dd2aed5 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2171,6 +2171,10 @@ public class Notification implements Parcelable } } + private void visitUris(@NonNull Consumer<Uri> visitor) { + visitIconUri(visitor, getIcon()); + } + @Override public Action clone() { return new Action( @@ -2858,7 +2862,7 @@ public class Notification implements Parcelable if (actions != null) { for (Action action : actions) { - visitIconUri(visitor, action.getIcon()); + action.visitUris(visitor); } } @@ -2939,6 +2943,11 @@ public class Notification implements Parcelable if (mBubbleMetadata != null) { visitIconUri(visitor, mBubbleMetadata.getIcon()); } + + if (extras != null && extras.containsKey(WearableExtender.EXTRA_WEARABLE_EXTENSIONS)) { + WearableExtender extender = new WearableExtender(this); + extender.visitUris(visitor); + } } /** @@ -3038,10 +3047,9 @@ public class Notification implements Parcelable // cannot look into the extras as there may be parcelables there that // the platform does not know how to handle. To go around that we have // an explicit list of the pending intents in the extras bundle. - final boolean collectPendingIntents = (allPendingIntents == null); - if (collectPendingIntents) { - PendingIntent.setOnMarshaledListener( - (PendingIntent intent, Parcel out, int outFlags) -> { + PendingIntent.OnMarshaledListener addedListener = null; + if (allPendingIntents == null) { + addedListener = (PendingIntent intent, Parcel out, int outFlags) -> { if (parcel == out) { synchronized (this) { if (allPendingIntents == null) { @@ -3050,7 +3058,8 @@ public class Notification implements Parcelable allPendingIntents.add(intent); } } - }); + }; + PendingIntent.addOnMarshaledListener(addedListener); } try { // IMPORTANT: Add marshaling code in writeToParcelImpl as we @@ -3061,8 +3070,8 @@ public class Notification implements Parcelable parcel.writeArraySet(allPendingIntents); } } finally { - if (collectPendingIntents) { - PendingIntent.setOnMarshaledListener(null); + if (addedListener != null) { + PendingIntent.removeOnMarshaledListener(addedListener); } } } @@ -3468,8 +3477,11 @@ public class Notification implements Parcelable * * @hide */ - public void setAllowlistToken(@Nullable IBinder token) { - mAllowlistToken = token; + public void clearAllowlistToken() { + mAllowlistToken = null; + if (publicVersion != null) { + publicVersion.clearAllowlistToken(); + } } /** @@ -11715,6 +11727,12 @@ public class Notification implements Parcelable mFlags &= ~mask; } } + + private void visitUris(@NonNull Consumer<Uri> visitor) { + for (Action action : mActions) { + action.visitUris(visitor); + } + } } /** diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 705b5ee84d01..c7522b429dbc 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -64,6 +64,7 @@ import com.android.internal.os.IResultReceiver; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -391,11 +392,12 @@ public final class PendingIntent implements Parcelable { void onMarshaled(PendingIntent intent, Parcel parcel, int flags); } - private static final ThreadLocal<OnMarshaledListener> sOnMarshaledListener - = new ThreadLocal<>(); + private static final ThreadLocal<List<OnMarshaledListener>> sOnMarshaledListener = + ThreadLocal.withInitial(ArrayList::new); /** - * Registers an listener for pending intents being written to a parcel. + * Registers an listener for pending intents being written to a parcel. This replaces any + * listeners previously added. * * @param listener The listener, null to clear. * @@ -403,7 +405,27 @@ public final class PendingIntent implements Parcelable { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static void setOnMarshaledListener(OnMarshaledListener listener) { - sOnMarshaledListener.set(listener); + final List<OnMarshaledListener> listeners = sOnMarshaledListener.get(); + listeners.clear(); + if (listener != null) { + listeners.add(listener); + } + } + + /** + * Adds a listener for pending intents being written to a parcel. + * @hide + */ + static void addOnMarshaledListener(OnMarshaledListener listener) { + sOnMarshaledListener.get().add(listener); + } + + /** + * Removes a listener for pending intents being written to a parcel. + * @hide + */ + static void removeOnMarshaledListener(OnMarshaledListener listener) { + sOnMarshaledListener.get().remove(listener); } private static void checkPendingIntent(int flags, @NonNull Intent intent, @@ -1451,11 +1473,11 @@ public final class PendingIntent implements Parcelable { public void writeToParcel(Parcel out, int flags) { out.writeStrongBinder(mTarget.asBinder()); - OnMarshaledListener listener = sOnMarshaledListener.get(); - if (listener != null) { - listener.onMarshaled(this, out, flags); + final List<OnMarshaledListener> listeners = sOnMarshaledListener.get(); + final int numListeners = listeners.size(); + for (int i = 0; i < numListeners; i++) { + listeners.get(i).onMarshaled(this, out, flags); } - } public static final @NonNull Creator<PendingIntent> CREATOR = new Creator<PendingIntent>() { @@ -1483,9 +1505,10 @@ public final class PendingIntent implements Parcelable { @NonNull Parcel out) { out.writeStrongBinder(sender != null ? sender.mTarget.asBinder() : null); if (sender != null) { - OnMarshaledListener listener = sOnMarshaledListener.get(); - if (listener != null) { - listener.onMarshaled(sender, out, 0 /* flags */); + final List<OnMarshaledListener> listeners = sOnMarshaledListener.get(); + final int numListeners = listeners.size(); + for (int i = 0; i < numListeners; i++) { + listeners.get(i).onMarshaled(sender, out, 0 /* flags */); } } } diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index 9d5073e43957..7080133dc597 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -177,5 +177,5 @@ interface IFaceService { // Internal operation used to clear face biometric scheduler. // Ensures that the scheduler is not stuck. @EnforcePermission("USE_BIOMETRIC_INTERNAL") - void scheduleWatchdog(); + oneway void scheduleWatchdog(); } diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 99758525e5ff..e2840ec20ff9 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -213,5 +213,5 @@ interface IFingerprintService { // Internal operation used to clear fingerprint biometric scheduler. // Ensures that the scheduler is not stuck. @EnforcePermission("USE_BIOMETRIC_INTERNAL") - void scheduleWatchdog(); + oneway void scheduleWatchdog(); } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index c1eacb535d57..1d5826807f36 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -302,11 +302,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation public static final int ANIMATION_TYPE_NONE = -1; /** Running animation will show insets */ - @VisibleForTesting public static final int ANIMATION_TYPE_SHOW = 0; /** Running animation will hide insets */ - @VisibleForTesting public static final int ANIMATION_TYPE_HIDE = 1; /** Running animation is controlled by user via {@link #controlWindowInsetsAnimation} */ @@ -447,21 +445,21 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (mInputMethodJankContext == null) return; ImeTracker.forJank().onRequestAnimation( mInputMethodJankContext, - mShow ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, + getAnimationType(), !mHasAnimationCallbacks); } @Override public void onAnimationCancel(Animator animation) { if (mInputMethodJankContext == null) return; - ImeTracker.forJank().onCancelAnimation(); + ImeTracker.forJank().onCancelAnimation(getAnimationType()); } @Override public void onAnimationEnd(Animator animation) { onAnimationFinish(); if (mInputMethodJankContext == null) return; - ImeTracker.forJank().onFinishAnimation(); + ImeTracker.forJank().onFinishAnimation(getAnimationType()); } }); if (!mHasAnimationCallbacks) { @@ -562,6 +560,14 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } } } + + /** + * Returns the current animation type. + */ + @AnimationType + private int getAnimationType() { + return mShow ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE; + } } /** diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 1e268bed3b17..5b69d7fdeb35 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1340,7 +1340,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession) .setName(name) .setLocalOwnerView(this) - .setParent(viewRoot.getBoundsLayer()) + .setParent(viewRoot.updateAndGetBoundsLayer(surfaceUpdateTransaction)) .setCallsite("SurfaceView.updateSurface") .setContainerLayer() .build(); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index bef28b2789b8..9d2bb58cdf0f 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -716,9 +716,9 @@ public final class ViewRootImpl implements ViewParent, /** * Child container layer of {@code mSurface} with the same bounds as its parent, and cropped to - * the surface insets. This surface is created only if a client requests it via {@link - * #getBoundsLayer()}. By parenting to this bounds surface, child surfaces can ensure they do - * not draw into the surface inset region set by the parent window. + * the surface insets. This surface is created only if a client requests it via + * {@link #updateAndGetBoundsLayer(Transaction)}. By parenting to this bounds surface, child + * surfaces can ensure they do not draw into the surface inset region set by the parent window. */ private SurfaceControl mBoundsLayer; private final SurfaceSession mSurfaceSession = new SurfaceSession(); @@ -2262,7 +2262,7 @@ public final class ViewRootImpl implements ViewParent, * <p>Parenting to this layer will ensure that its children are cropped by the view's surface * insets. */ - public SurfaceControl getBoundsLayer() { + public SurfaceControl updateAndGetBoundsLayer(Transaction t) { if (mBoundsLayer == null) { mBoundsLayer = new SurfaceControl.Builder(mSurfaceSession) .setContainerLayer() @@ -2270,8 +2270,8 @@ public final class ViewRootImpl implements ViewParent, .setParent(getSurfaceControl()) .setCallsite("ViewRootImpl.getBoundsLayer") .build(); - setBoundsLayerCrop(mTransaction); - mTransaction.show(mBoundsLayer).apply(); + setBoundsLayerCrop(t); + t.show(mBoundsLayer); } return mBoundsLayer; } @@ -11188,7 +11188,8 @@ public final class ViewRootImpl implements ViewParent, @Nullable public SurfaceControl.Transaction buildReparentTransaction( @NonNull SurfaceControl child) { if (mSurfaceControl.isValid()) { - return new SurfaceControl.Transaction().reparent(child, getBoundsLayer()); + Transaction t = new Transaction(); + return t.reparent(child, updateAndGetBoundsLayer(t)); } return null; } diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java index f0d1019ffb06..03d1cd8f89e3 100644 --- a/core/java/android/view/inputmethod/ImeTracker.java +++ b/core/java/android/view/inputmethod/ImeTracker.java @@ -16,8 +16,12 @@ package android.view.inputmethod; +import static android.view.InsetsController.ANIMATION_TYPE_HIDE; +import static android.view.InsetsController.ANIMATION_TYPE_SHOW; + import static com.android.internal.inputmethod.InputMethodDebug.softInputDisplayReasonToString; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_ANIMATION; +import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_HIDE_ANIMATION; +import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_SHOW_ANIMATION; import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_HIDDEN; import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_SHOWN; @@ -696,20 +700,22 @@ public interface ImeTracker { /** * Called when the animation, which is going to be monitored, starts. * - * @param jankContext context which is needed by {@link InteractionJankMonitor} - * @param animType {@link AnimationType} + * @param jankContext context which is needed by {@link InteractionJankMonitor}. + * @param animType the animation type. * @param useSeparatedThread {@code true} if the animation is handled by the app, * {@code false} if the animation will be scheduled on the - * {@link android.view.InsetsAnimationThread} + * {@link android.view.InsetsAnimationThread}. */ public void onRequestAnimation(@NonNull InputMethodJankContext jankContext, @AnimationType int animType, boolean useSeparatedThread) { + final int cujType = getImeInsetsCujFromAnimation(animType); if (jankContext.getDisplayContext() == null - || jankContext.getTargetSurfaceControl() == null) { + || jankContext.getTargetSurfaceControl() == null + || cujType == -1) { return; } final Configuration.Builder builder = Configuration.Builder.withSurface( - CUJ_IME_INSETS_ANIMATION, + cujType, jankContext.getDisplayContext(), jankContext.getTargetSurfaceControl()) .setTag(String.format(Locale.US, "%d@%d@%s", animType, @@ -719,16 +725,44 @@ public interface ImeTracker { /** * Called when the animation, which is going to be monitored, cancels. + * + * @param animType the animation type. */ - public void onCancelAnimation() { - InteractionJankMonitor.getInstance().cancel(CUJ_IME_INSETS_ANIMATION); + public void onCancelAnimation(@AnimationType int animType) { + final int cujType = getImeInsetsCujFromAnimation(animType); + if (cujType == -1) { + InteractionJankMonitor.getInstance().cancel(cujType); + } } /** * Called when the animation, which is going to be monitored, ends. + * + * @param animType the animation type. */ - public void onFinishAnimation() { - InteractionJankMonitor.getInstance().end(CUJ_IME_INSETS_ANIMATION); + public void onFinishAnimation(@AnimationType int animType) { + final int cujType = getImeInsetsCujFromAnimation(animType); + if (cujType != -1) { + InteractionJankMonitor.getInstance().end(cujType); + } + } + + /** + * A helper method to translate animation type to CUJ type for IME animations. + * + * @param animType the animation type. + * @return the integer in {@link com.android.internal.jank.InteractionJankMonitor.CujType}, + * or {@code -1} if the animation type is not supported for tracking yet. + */ + private static int getImeInsetsCujFromAnimation(@AnimationType int animType) { + switch (animType) { + case ANIMATION_TYPE_SHOW: + return CUJ_IME_INSETS_SHOW_ANIMATION; + case ANIMATION_TYPE_HIDE: + return CUJ_IME_INSETS_HIDE_ANIMATION; + default: + return -1; + } } } diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index 9f804b1e1a7f..3323ae576f33 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -688,6 +688,7 @@ public final class WindowContainerTransaction implements Parcelable { .setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type) .setSource(InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE) .setArbitraryRectangle(frame)) + .setInsetsFrameOwner(owner) .build(); mHierarchyOps.add(hierarchyOp); return this; @@ -712,6 +713,7 @@ public final class WindowContainerTransaction implements Parcelable { new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER) .setContainer(receiver.asBinder()) .setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type)) + .setInsetsFrameOwner(owner) .build(); mHierarchyOps.add(hierarchyOp); return this; @@ -1344,8 +1346,12 @@ public final class WindowContainerTransaction implements Parcelable { @Nullable private IBinder mReparent; + @Nullable private InsetsFrameProvider mInsetsFrameProvider; + @Nullable + private IBinder mInsetsFrameOwner; + // Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom. private boolean mToTop; @@ -1478,6 +1484,7 @@ public final class WindowContainerTransaction implements Parcelable { mContainer = copy.mContainer; mReparent = copy.mReparent; mInsetsFrameProvider = copy.mInsetsFrameProvider; + mInsetsFrameOwner = copy.mInsetsFrameOwner; mToTop = copy.mToTop; mReparentTopOnly = copy.mReparentTopOnly; mWindowingModes = copy.mWindowingModes; @@ -1496,6 +1503,7 @@ public final class WindowContainerTransaction implements Parcelable { mContainer = in.readStrongBinder(); mReparent = in.readStrongBinder(); mInsetsFrameProvider = in.readTypedObject(InsetsFrameProvider.CREATOR); + mInsetsFrameOwner = in.readStrongBinder(); mToTop = in.readBoolean(); mReparentTopOnly = in.readBoolean(); mWindowingModes = in.createIntArray(); @@ -1527,6 +1535,11 @@ public final class WindowContainerTransaction implements Parcelable { return mInsetsFrameProvider; } + @Nullable + public IBinder getInsetsFrameOwner() { + return mInsetsFrameOwner; + } + @NonNull public IBinder getContainer() { return mContainer; @@ -1657,7 +1670,8 @@ public final class WindowContainerTransaction implements Parcelable { case HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER: case HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER: sb.append("container=").append(mContainer) - .append(" provider=").append(mInsetsFrameProvider); + .append(" provider=").append(mInsetsFrameProvider) + .append(" owner=").append(mInsetsFrameOwner); break; case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: sb.append("container=").append(mContainer) @@ -1697,6 +1711,7 @@ public final class WindowContainerTransaction implements Parcelable { dest.writeStrongBinder(mContainer); dest.writeStrongBinder(mReparent); dest.writeTypedObject(mInsetsFrameProvider, flags); + dest.writeStrongBinder(mInsetsFrameOwner); dest.writeBoolean(mToTop); dest.writeBoolean(mReparentTopOnly); dest.writeIntArray(mWindowingModes); @@ -1737,8 +1752,12 @@ public final class WindowContainerTransaction implements Parcelable { @Nullable private IBinder mReparent; + @Nullable private InsetsFrameProvider mInsetsFrameProvider; + @Nullable + private IBinder mInsetsFrameOwner; + private boolean mToTop; private boolean mReparentTopOnly; @@ -1782,8 +1801,13 @@ public final class WindowContainerTransaction implements Parcelable { return this; } - Builder setInsetsFrameProvider(InsetsFrameProvider providers) { - mInsetsFrameProvider = providers; + Builder setInsetsFrameProvider(InsetsFrameProvider provider) { + mInsetsFrameProvider = provider; + return this; + } + + Builder setInsetsFrameOwner(IBinder owner) { + mInsetsFrameOwner = owner; return this; } @@ -1854,6 +1878,7 @@ public final class WindowContainerTransaction implements Parcelable { ? Arrays.copyOf(mActivityTypes, mActivityTypes.length) : null; hierarchyOp.mInsetsFrameProvider = mInsetsFrameProvider; + hierarchyOp.mInsetsFrameOwner = mInsetsFrameOwner; hierarchyOp.mToTop = mToTop; hierarchyOp.mReparentTopOnly = mReparentTopOnly; hierarchyOp.mLaunchOptions = mLaunchOptions; diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 92427ec75f37..03d74502e191 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -25,7 +25,8 @@ import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT; import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL; import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK; @@ -257,7 +258,6 @@ public class InteractionJankMonitor { public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66; public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE = 67; public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68; - public static final int CUJ_IME_INSETS_ANIMATION = 69; public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = 70; public static final int CUJ_LAUNCHER_OPEN_SEARCH_RESULT = 71; // 72 - 77 are reserved for b/281564325. @@ -269,8 +269,10 @@ public class InteractionJankMonitor { */ public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK = 78; public static final int CUJ_SHADE_EXPAND_FROM_STATUS_BAR = 79; + public static final int CUJ_IME_INSETS_SHOW_ANIMATION = 80; + public static final int CUJ_IME_INSETS_HIDE_ANIMATION = 81; - private static final int LAST_CUJ = CUJ_SHADE_EXPAND_FROM_STATUS_BAR; + private static final int LAST_CUJ = CUJ_IME_INSETS_HIDE_ANIMATION; private static final int NO_STATSD_LOGGING = -1; // Used to convert CujType to InteractionType enum value for statsd logging. @@ -348,7 +350,7 @@ public class InteractionJankMonitor { CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION; + CUJ_TO_STATSD_INTERACTION_TYPE[69] = NO_STATSD_LOGGING; // This is deprecated. CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_SEARCH_RESULT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT; // 72 - 77 are reserved for b/281564325. @@ -360,6 +362,8 @@ public class InteractionJankMonitor { CUJ_TO_STATSD_INTERACTION_TYPE[77] = NO_STATSD_LOGGING; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_EXPAND_FROM_STATUS_BAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_FROM_STATUS_BAR; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION; } private static class InstanceHolder { @@ -456,11 +460,12 @@ public class InteractionJankMonitor { CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS, CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE, CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME, - CUJ_IME_INSETS_ANIMATION, CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION, CUJ_LAUNCHER_OPEN_SEARCH_RESULT, CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK, CUJ_SHADE_EXPAND_FROM_STATUS_BAR, + CUJ_IME_INSETS_SHOW_ANIMATION, + CUJ_IME_INSETS_HIDE_ANIMATION, }) @Retention(RetentionPolicy.SOURCE) public @interface CujType { @@ -1066,8 +1071,6 @@ public class InteractionJankMonitor { return "LAUNCHER_CLOSE_ALL_APPS_SWIPE"; case CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME: return "LAUNCHER_CLOSE_ALL_APPS_TO_HOME"; - case CUJ_IME_INSETS_ANIMATION: - return "IME_INSETS_ANIMATION"; case CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION: return "LOCKSCREEN_CLOCK_MOVE_ANIMATION"; case CUJ_LAUNCHER_OPEN_SEARCH_RESULT: @@ -1076,6 +1079,10 @@ public class InteractionJankMonitor { return "LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK"; case CUJ_SHADE_EXPAND_FROM_STATUS_BAR: return "SHADE_EXPAND_FROM_STATUS_BAR"; + case CUJ_IME_INSETS_SHOW_ANIMATION: + return "IME_INSETS_SHOW_ANIMATION"; + case CUJ_IME_INSETS_HIDE_ANIMATION: + return "IME_INSETS_HIDE_ANIMATION"; } return "UNKNOWN"; } diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index eba7f587bb8b..7c69e65fecd7 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -271,6 +271,54 @@ public class NotificationTest { } @Test + public void allPendingIntents_resilientToAnotherNotificationInExtras() { + PendingIntent contentIntent = createPendingIntent("content"); + PendingIntent actionIntent = createPendingIntent("action"); + Notification another = new Notification.Builder(mContext, "channel").build(); + Bundle bundleContainingAnotherNotification = new Bundle(); + bundleContainingAnotherNotification.putParcelable(null, another); + Notification source = new Notification.Builder(mContext, "channel") + .setContentIntent(contentIntent) + .addAction(new Notification.Action.Builder(null, "action", actionIntent).build()) + .setExtras(bundleContainingAnotherNotification) + .build(); + + Parcel p = Parcel.obtain(); + source.writeToParcel(p, 0); + p.setDataPosition(0); + Notification unparceled = new Notification(p); + + assertThat(unparceled.allPendingIntents).containsExactly(contentIntent, actionIntent); + } + + @Test + public void allPendingIntents_alsoInPublicVersion() { + PendingIntent contentIntent = createPendingIntent("content"); + PendingIntent actionIntent = createPendingIntent("action"); + PendingIntent publicContentIntent = createPendingIntent("publicContent"); + PendingIntent publicActionIntent = createPendingIntent("publicAction"); + Notification source = new Notification.Builder(mContext, "channel") + .setContentIntent(contentIntent) + .addAction(new Notification.Action.Builder(null, "action", actionIntent).build()) + .setPublicVersion(new Notification.Builder(mContext, "channel") + .setContentIntent(publicContentIntent) + .addAction(new Notification.Action.Builder( + null, "publicAction", publicActionIntent).build()) + .build()) + .build(); + + Parcel p = Parcel.obtain(); + source.writeToParcel(p, 0); + p.setDataPosition(0); + Notification unparceled = new Notification(p); + + assertThat(unparceled.allPendingIntents).containsExactly(contentIntent, actionIntent, + publicContentIntent, publicActionIntent); + assertThat(unparceled.publicVersion.allPendingIntents).containsExactly(publicContentIntent, + publicActionIntent); + } + + @Test public void messagingStyle_isGroupConversation() { mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.P; Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name") diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java index 8f83461baea7..b61f995724e5 100644 --- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java +++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java @@ -79,6 +79,7 @@ import org.mockito.ArgumentCaptor; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; @@ -93,6 +94,8 @@ public class InteractionJankMonitorTest { private static final String ENUM_NAME_PREFIX = "UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__"; + private static final ArrayList<String> DEPRECATED_VALUES = new ArrayList<>(); + private ViewAttachTestActivity mActivity; private View mView; private HandlerThread mWorker; @@ -126,6 +129,7 @@ public class InteractionJankMonitorTest { CUJ_NOTIFICATION_SHADE_ROW_SWIPE, getEnumName("SHADE_ROW_SWIPE")); ENUM_NAME_EXCEPTION_MAP.put( CUJ_NOTIFICATION_SHADE_SCROLL_FLING, getEnumName("SHADE_SCROLL_FLING")); + DEPRECATED_VALUES.add(ENUM_NAME_PREFIX + "IME_INSETS_ANIMATION"); } private static String getEnumName(String name) { @@ -239,6 +243,7 @@ public class InteractionJankMonitorTest { Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields()) .filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX) + && !DEPRECATED_VALUES.contains(f.getName()) && Modifier.isStatic(f.getModifiers()) && f.getType() == int.class) .collect(Collectors.toMap(this::getIntFieldChecked, Field::getName)); diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 14e82539a6a5..3d4b55acee39 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -228,12 +228,12 @@ <dimen name="bubble_user_education_stack_padding">16dp</dimen> <!-- Size of the bubble bar (height), should match transient_taskbar_size in Launcher. --> <dimen name="bubblebar_size">72dp</dimen> - <!-- The size of the drag handle / menu shown along with a bubble bar expanded view. --> - <dimen name="bubble_bar_expanded_view_handle_size">40dp</dimen> - <!-- The width of the drag handle shown along with a bubble bar expanded view. --> - <dimen name="bubble_bar_expanded_view_handle_width">128dp</dimen> - <!-- The height of the drag handle shown along with a bubble bar expanded view. --> - <dimen name="bubble_bar_expanded_view_handle_height">4dp</dimen> + <!-- The size of the caption bar inset at the top of bubble bar expanded view. --> + <dimen name="bubble_bar_expanded_view_caption_height">32dp</dimen> + <!-- The height of the dots shown for the caption menu in the bubble bar expanded view.. --> + <dimen name="bubble_bar_expanded_view_caption_dot_size">4dp</dimen> + <!-- The spacing between the dots for the caption menu in the bubble bar expanded view.. --> + <dimen name="bubble_bar_expanded_view_caption_dot_spacing">4dp</dimen> <!-- Minimum width of the bubble bar manage menu. --> <dimen name="bubble_bar_manage_menu_min_width">200dp</dimen> <!-- Size of the dismiss icon in the bubble bar manage menu. --> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java index f729d029a818..e97390d3a86e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java @@ -25,6 +25,8 @@ import android.graphics.PointF; import android.util.Log; import android.widget.FrameLayout; +import androidx.annotation.Nullable; + import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.animation.PhysicsAnimator; import com.android.wm.shell.bubbles.BubbleOverflow; @@ -111,7 +113,8 @@ public class BubbleBarAnimationHelper { /** * Animates the provided bubble's expanded view to the expanded state. */ - public void animateExpansion(BubbleViewProvider expandedBubble) { + public void animateExpansion(BubbleViewProvider expandedBubble, + @Nullable Runnable afterAnimation) { mExpandedBubble = expandedBubble; if (mExpandedBubble == null) { return; @@ -160,6 +163,9 @@ public class BubbleBarAnimationHelper { bev.setAnimationMatrix(null); updateExpandedView(); bev.setSurfaceZOrderedOnTop(false); + if (afterAnimation != null) { + afterAnimation.run(); + } }) .start(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java index 396aa0e56cf6..6b6d6baa3d39 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java @@ -16,12 +16,12 @@ package com.android.wm.shell.bubbles.bar; -import android.annotation.ColorInt; import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; +import android.graphics.Insets; import android.graphics.Outline; import android.graphics.Rect; import android.util.AttributeSet; @@ -63,7 +63,8 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView private @Nullable TaskView mTaskView; private @Nullable BubbleOverflowContainerView mOverflowView; - private int mHandleHeight; + private int mCaptionHeight; + private int mBackgroundColor; private float mCornerRadius = 0f; @@ -97,8 +98,8 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView super.onFinishInflate(); Context context = getContext(); setElevation(getResources().getDimensionPixelSize(R.dimen.bubble_elevation)); - mHandleHeight = context.getResources().getDimensionPixelSize( - R.dimen.bubble_bar_expanded_view_handle_size); + mCaptionHeight = context.getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_caption_height); addView(mHandleView); applyThemeAttrs(); setClipToOutline(true); @@ -136,6 +137,9 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView addView(mTaskView); mTaskView.setEnableSurfaceClipping(true); mTaskView.setCornerRadius(mCornerRadius); + + // Handle view needs to draw on top of task view. + bringChildToFront(mHandleView); } mMenuViewController = new BubbleBarMenuViewController(mContext, this); mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() { @@ -169,6 +173,10 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView }); } + public BubbleBarHandleView getHandleView() { + return mHandleView; + } + // TODO (b/275087636): call this when theme/config changes /** Updates the view based on the current theme. */ public void applyThemeAttrs() { @@ -183,12 +191,12 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView ta.recycle(); - mHandleHeight = getResources().getDimensionPixelSize( - R.dimen.bubble_bar_expanded_view_handle_size); + mCaptionHeight = getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_caption_height); if (mTaskView != null) { mTaskView.setCornerRadius(mCornerRadius); - updateHandleAndBackgroundColor(true /* animated */); + updateHandleColor(true /* animated */); } } @@ -196,13 +204,12 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); - int menuViewHeight = Math.min(mHandleHeight, height); + int menuViewHeight = Math.min(mCaptionHeight, height); measureChild(mHandleView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight, MeasureSpec.getMode(heightMeasureSpec))); if (mTaskView != null) { - int taskViewHeight = height - menuViewHeight; - measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(taskViewHeight, + measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.getMode(heightMeasureSpec))); } } @@ -210,19 +217,20 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); - // Drag handle above - final int dragHandleBottom = t + mHandleView.getMeasuredHeight(); - mHandleView.layout(l, t, r, dragHandleBottom); + final int captionBottom = t + mCaptionHeight; if (mTaskView != null) { - mTaskView.layout(l, dragHandleBottom, r, - dragHandleBottom + mTaskView.getMeasuredHeight()); + mTaskView.layout(l, t, r, + t + mTaskView.getMeasuredHeight()); + mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0)); } + // Handle draws on top of task view in the caption area. + mHandleView.layout(l, t, r, captionBottom); } @Override public void onTaskCreated() { setContentVisibility(true); - updateHandleAndBackgroundColor(false /* animated */); + updateHandleColor(false /* animated */); } @Override @@ -298,33 +306,20 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView } /** - * Updates the background color to match with task view status/bg color, and sets handle color - * to contrast with the background - */ - private void updateHandleAndBackgroundColor(boolean animated) { - if (mTaskView == null) return; - final int color = getTaskViewColor(); - final boolean isRegionDark = Color.luminance(color) <= 0.5; - mHandleView.updateHandleColor(isRegionDark, animated); - setBackgroundColor(color); - } - - /** - * Retrieves task view status/nav bar color or background if available - * - * TODO (b/283075226): Update with color sampling when - * RegionSamplingHelper or alternative is available + * Updates the handle color based on the task view status bar or background color; if those + * are transparent it defaults to the background color pulled from system theme attributes. */ - private @ColorInt int getTaskViewColor() { - if (mTaskView == null || mTaskView.getTaskInfo() == null) return mBackgroundColor; + private void updateHandleColor(boolean animated) { + if (mTaskView == null || mTaskView.getTaskInfo() == null) return; + int color = mBackgroundColor; ActivityManager.TaskDescription taskDescription = mTaskView.getTaskInfo().taskDescription; if (taskDescription.getStatusBarColor() != Color.TRANSPARENT) { - return taskDescription.getStatusBarColor(); + color = taskDescription.getStatusBarColor(); } else if (taskDescription.getBackgroundColor() != Color.TRANSPARENT) { - return taskDescription.getBackgroundColor(); - } else { - return mBackgroundColor; + color = taskDescription.getBackgroundColor(); } + final boolean isRegionDark = Color.luminance(color) <= 0.5; + mHandleView.updateHandleColor(isRegionDark, animated); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java index ce26bc0322b3..2b7a0706b4de 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java @@ -21,7 +21,8 @@ import android.animation.ObjectAnimator; import android.annotation.Nullable; import android.content.Context; import android.graphics.Outline; -import android.graphics.Rect; +import android.graphics.Path; +import android.graphics.RectF; import android.util.AttributeSet; import android.view.View; import android.view.ViewOutlineProvider; @@ -37,8 +38,12 @@ import com.android.wm.shell.R; public class BubbleBarHandleView extends View { private static final long COLOR_CHANGE_DURATION = 120; - private int mHandleWidth; - private int mHandleHeight; + // The handle view is currently rendered as 3 evenly spaced dots. + private int mDotSize; + private int mDotSpacing; + // Path used to draw the dots + private final Path mPath = new Path(); + private @ColorInt int mHandleLightColor; private @ColorInt int mHandleDarkColor; private @Nullable ObjectAnimator mColorChangeAnim; @@ -58,11 +63,10 @@ public class BubbleBarHandleView extends View { public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - - mHandleWidth = getResources().getDimensionPixelSize( - R.dimen.bubble_bar_expanded_view_handle_width); - mHandleHeight = getResources().getDimensionPixelSize( - R.dimen.bubble_bar_expanded_view_handle_height); + mDotSize = getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_caption_dot_size); + mDotSpacing = getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_caption_dot_spacing); mHandleLightColor = ContextCompat.getColor(getContext(), R.color.bubble_bar_expanded_view_handle_light); mHandleDarkColor = ContextCompat.getColor(getContext(), @@ -74,13 +78,26 @@ public class BubbleBarHandleView extends View { public void getOutline(View view, Outline outline) { final int handleCenterX = view.getWidth() / 2; final int handleCenterY = view.getHeight() / 2; - final float handleRadius = mHandleHeight / 2f; - Rect handleBounds = new Rect( - handleCenterX - mHandleWidth / 2, - handleCenterY - mHandleHeight / 2, - handleCenterX + mHandleWidth / 2, - handleCenterY + mHandleHeight / 2); - outline.setRoundRect(handleBounds, handleRadius); + final int handleTotalWidth = mDotSize * 3 + mDotSpacing * 2; + final int handleLeft = handleCenterX - handleTotalWidth / 2; + final int handleTop = handleCenterY - mDotSize / 2; + final int handleBottom = handleTop + mDotSize; + RectF dot1 = new RectF( + handleLeft, handleTop, + handleLeft + mDotSize, handleBottom); + RectF dot2 = new RectF( + dot1.right + mDotSpacing, handleTop, + dot1.right + mDotSpacing + mDotSize, handleBottom + ); + RectF dot3 = new RectF( + dot2.right + mDotSpacing, handleTop, + dot2.right + mDotSpacing + mDotSize, handleBottom + ); + mPath.reset(); + mPath.addOval(dot1, Path.Direction.CW); + mPath.addOval(dot2, Path.Direction.CW); + mPath.addOval(dot3, Path.Direction.CW); + outline.setPath(mPath); } }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index d20b33edf2c9..8ead18b139de 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -24,6 +24,7 @@ import android.content.Context; import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.ColorDrawable; +import android.view.TouchDelegate; import android.view.View; import android.view.ViewTreeObserver; import android.widget.FrameLayout; @@ -68,6 +69,10 @@ public class BubbleBarLayerView extends FrameLayout private final Region mTouchableRegion = new Region(); private final Rect mTempRect = new Rect(); + // Used to ensure touch target size for the menu shown on a bubble expanded view + private TouchDelegate mHandleTouchDelegate; + private final Rect mHandleTouchBounds = new Rect(); + public BubbleBarLayerView(Context context, BubbleController controller) { super(context); mBubbleController = controller; @@ -164,7 +169,17 @@ public class BubbleBarLayerView extends FrameLayout mIsExpanded = true; mBubbleController.getSysuiProxy().onStackExpandChanged(true); - mAnimationHelper.animateExpansion(mExpandedBubble); + mAnimationHelper.animateExpansion(mExpandedBubble, () -> { + if (mExpandedView == null) return; + // Touch delegate for the menu + BubbleBarHandleView view = mExpandedView.getHandleView(); + view.getBoundsOnScreen(mHandleTouchBounds); + mHandleTouchBounds.top -= mPositioner.getBubblePaddingTop(); + mHandleTouchDelegate = new TouchDelegate(mHandleTouchBounds, + mExpandedView.getHandleView()); + setTouchDelegate(mHandleTouchDelegate); + }); + showScrim(true); } @@ -175,6 +190,7 @@ public class BubbleBarLayerView extends FrameLayout mAnimationHelper.animateCollapse(() -> removeView(viewToRemove)); mBubbleController.getSysuiProxy().onStackExpandChanged(false); mExpandedView = null; + setTouchDelegate(null); showScrim(false); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java index 262d487b1d1b..2dbc4445d606 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java @@ -434,7 +434,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { "Set divider bar %s from %s", interactive ? "interactive" : "non-interactive", from); mInteractive = interactive; - if (!mInteractive && mMoving) { + if (!mInteractive && hideHandle && mMoving) { final int position = mSplitLayout.getDividePosition(); mSplitLayout.flingDividePosition( mLastDraggingPosition, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java index a9ccdf6a156f..2b1037711249 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java @@ -323,7 +323,11 @@ public class SplitDecorManager extends WindowlessWindowManager { } } if (mShown) { - fadeOutDecor(()-> animFinishedCallback.accept(true)); + fadeOutDecor(()-> { + if (mRunningAnimationCount == 0 && animFinishedCallback != null) { + animFinishedCallback.accept(true); + } + }); } else { // Decor surface is hidden so release it directly. releaseDecor(t); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index f70d3aec9ec8..e8fa638bea31 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -593,9 +593,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange void flingDividePosition(int from, int to, int duration, @Nullable Runnable flingFinishedCallback) { if (from == to) { - // No animation run, still callback to stop resizing. - mSplitLayoutHandler.onLayoutSizeChanged(this); - if (flingFinishedCallback != null) { flingFinishedCallback.run(); } @@ -773,15 +770,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) { boolean boundsChanged = false; if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) { - wct.setBounds(task1.token, mBounds1); - wct.setSmallestScreenWidthDp(task1.token, getSmallestWidthDp(mBounds1)); + setTaskBounds(wct, task1, mBounds1); mWinBounds1.set(mBounds1); mWinToken1 = task1.token; boundsChanged = true; } if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) { - wct.setBounds(task2.token, mBounds2); - wct.setSmallestScreenWidthDp(task2.token, getSmallestWidthDp(mBounds2)); + setTaskBounds(wct, task2, mBounds2); mWinBounds2.set(mBounds2); mWinToken2 = task2.token; boundsChanged = true; @@ -789,6 +784,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange return boundsChanged; } + /** Set bounds to the {@link WindowContainerTransaction} for single task. */ + public void setTaskBounds(WindowContainerTransaction wct, + ActivityManager.RunningTaskInfo task, Rect bounds) { + wct.setBounds(task.token, bounds); + wct.setSmallestScreenWidthDp(task.token, getSmallestWidthDp(bounds)); + } + private int getSmallestWidthDp(Rect bounds) { mTempRect.set(bounds); mTempRect.inset(getDisplayStableInsets(mContext)); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 40ea2764d295..6d14440c9b18 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -63,6 +63,7 @@ import com.android.wm.shell.sysui.ShellSharedConstants import com.android.wm.shell.transition.Transitions import com.android.wm.shell.util.KtProtoLog import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration +import com.android.wm.shell.windowdecor.MoveToDesktopAnimator import java.io.PrintWriter import java.util.concurrent.Executor import java.util.function.Consumer @@ -204,7 +205,11 @@ class DesktopTasksController( * Moves a single task to freeform and sets the taskBounds to the passed in bounds, * startBounds */ - fun moveToFreeform(taskInfo: RunningTaskInfo, startBounds: Rect) { + fun moveToFreeform( + taskInfo: RunningTaskInfo, + startBounds: Rect, + dragToDesktopValueAnimator: MoveToDesktopAnimator + ) { KtProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: moveToFreeform with bounds taskId=%d", @@ -216,8 +221,8 @@ class DesktopTasksController( wct.setBounds(taskInfo.token, startBounds) if (Transitions.ENABLE_SHELL_TRANSITIONS) { - enterDesktopTaskTransitionHandler.startTransition( - Transitions.TRANSIT_ENTER_FREEFORM, wct, mOnAnimationFinishedCallback) + enterDesktopTaskTransitionHandler.startMoveToFreeformAnimation(wct, + dragToDesktopValueAnimator, mOnAnimationFinishedCallback) } else { shellTaskOrganizer.applyTransaction(wct) } @@ -270,7 +275,7 @@ class DesktopTasksController( * Move a task to fullscreen after being dragged from fullscreen and released back into * status bar area */ - fun cancelMoveToFreeform(task: RunningTaskInfo, position: Point) { + fun cancelMoveToFreeform(task: RunningTaskInfo, moveToDesktopAnimator: MoveToDesktopAnimator) { KtProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: cancelMoveToFreeform taskId=%d", @@ -280,8 +285,8 @@ class DesktopTasksController( wct.setBounds(task.token, null) if (Transitions.ENABLE_SHELL_TRANSITIONS) { - enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode( - wct, position) { t -> + enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, + moveToDesktopAnimator) { t -> val callbackWCT = WindowContainerTransaction() visualIndicator?.releaseVisualIndicator(t) visualIndicator = null diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java index 3e175f3a9ae2..650cac5cb999 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java @@ -22,9 +22,10 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.app.ActivityManager; -import android.graphics.Point; +import android.graphics.PointF; import android.graphics.Rect; import android.os.IBinder; +import android.util.Slog; import android.view.SurfaceControl; import android.view.WindowManager; import android.window.TransitionInfo; @@ -35,6 +36,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.windowdecor.MoveToDesktopAnimator; import java.util.ArrayList; import java.util.List; @@ -47,18 +49,17 @@ import java.util.function.Supplier; */ public class EnterDesktopTaskTransitionHandler implements Transitions.TransitionHandler { + private static final String TAG = "EnterDesktopTaskTransitionHandler"; private final Transitions mTransitions; private final Supplier<SurfaceControl.Transaction> mTransactionSupplier; - // The size of the screen during drag relative to the fullscreen size - public static final float DRAG_FREEFORM_SCALE = 0.4f; // The size of the screen after drag relative to the fullscreen size public static final float FINAL_FREEFORM_SCALE = 0.6f; public static final int FREEFORM_ANIMATION_DURATION = 336; private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); - private Point mPosition; private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback; + private MoveToDesktopAnimator mMoveToDesktopAnimator; public EnterDesktopTaskTransitionHandler( Transitions transitions) { @@ -87,15 +88,30 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition } /** + * Starts Transition of type TRANSIT_ENTER_FREEFORM + * @param wct WindowContainerTransaction for transition + * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move + * to desktop animation + * @param onAnimationEndCallback to be called after animation + */ + public void startMoveToFreeformAnimation(@NonNull WindowContainerTransaction wct, + @NonNull MoveToDesktopAnimator moveToDesktopAnimator, + Consumer<SurfaceControl.Transaction> onAnimationEndCallback) { + mMoveToDesktopAnimator = moveToDesktopAnimator; + startTransition(Transitions.TRANSIT_ENTER_FREEFORM, wct, onAnimationEndCallback); + } + + /** * Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE * @param wct WindowContainerTransaction for transition - * @param position Position of task when transition is triggered + * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move + * to desktop animation * @param onAnimationEndCallback to be called after animation */ public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct, - Point position, + MoveToDesktopAnimator moveToDesktopAnimator, Consumer<SurfaceControl.Transaction> onAnimationEndCallback) { - mPosition = position; + mMoveToDesktopAnimator = moveToDesktopAnimator; startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct, onAnimationEndCallback); } @@ -145,9 +161,23 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition // to null and we don't require an animation final SurfaceControl sc = change.getLeash(); startT.setWindowCrop(sc, null); + + if (mMoveToDesktopAnimator == null + || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) { + Slog.e(TAG, "No animator available for this transition"); + return false; + } + + // Calculate and set position of the task + final PointF position = mMoveToDesktopAnimator.getPosition(); + startT.setPosition(sc, position.x, position.y); + finishT.setPosition(sc, position.x, position.y); + startT.apply(); + mTransitions.getMainExecutor().execute( () -> finishCallback.onTransitionFinished(null, null)); + return true; } @@ -162,12 +192,18 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition endBounds.height()); startT.apply(); + // End the animation that shrinks the window when task is first dragged from fullscreen + if (mMoveToDesktopAnimator != null) { + mMoveToDesktopAnimator.endAnimator(); + } + // We want to find the scale of the current bounds relative to the end bounds. The // task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be // scaled to FINAL_FREEFORM_SCALE. So, it is scaled to // DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds final ValueAnimator animator = - ValueAnimator.ofFloat(DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f); + ValueAnimator.ofFloat( + MoveToDesktopAnimator.DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f); animator.setDuration(FREEFORM_ANIMATION_DURATION); final SurfaceControl.Transaction t = mTransactionSupplier.get(); animator.addUpdateListener(animation -> { @@ -199,8 +235,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition } if (type == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE - && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM - && mPosition != null) { + && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { // This Transition animates a task to fullscreen after being dragged from the status // bar and then released back into the status bar area final SurfaceControl sc = change.getLeash(); @@ -210,13 +245,27 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition .setWindowCrop(sc, endBounds.width(), endBounds.height()) .apply(); + if (mMoveToDesktopAnimator == null + || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) { + Slog.e(TAG, "No animator available for this transition"); + return false; + } + + // End the animation that shrinks the window when task is first dragged from fullscreen + mMoveToDesktopAnimator.endAnimator(); + final ValueAnimator animator = new ValueAnimator(); - animator.setFloatValues(DRAG_FREEFORM_SCALE, 1f); + animator.setFloatValues(MoveToDesktopAnimator.DRAG_FREEFORM_SCALE, 1f); animator.setDuration(FREEFORM_ANIMATION_DURATION); final SurfaceControl.Transaction t = mTransactionSupplier.get(); + + // Get position of the task + final float x = mMoveToDesktopAnimator.getPosition().x; + final float y = mMoveToDesktopAnimator.getPosition().y; + animator.addUpdateListener(animation -> { final float scale = (float) animation.getAnimatedValue(); - t.setPosition(sc, mPosition.x * (1 - scale), mPosition.y * (1 - scale)) + t.setPosition(sc, x * (1 - scale), y * (1 - scale)) .setScale(sc, scale, scale) .show(sc) .apply(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 837f11803ab2..39b6675567e7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -573,10 +573,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } final boolean isRootTask = taskInfo != null && TransitionInfo.isIndependent(change, info); + final boolean isRecentsTask = mRecentsTask != null + && mRecentsTask.equals(change.getContainer()); hasTaskChange = hasTaskChange || isRootTask; final boolean isLeafTask = leafTaskFilter.test(change); if (TransitionUtil.isOpeningType(change.getMode())) { - if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) { + if (isRecentsTask) { recentsOpening = change; } else if (isRootTask || isLeafTask) { if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { @@ -591,7 +593,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { openingTaskIsLeafs.add(isLeafTask ? 1 : 0); } } else if (TransitionUtil.isClosingType(change.getMode())) { - if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) { + if (isRecentsTask) { foundRecentsClosing = true; } else if (isRootTask || isLeafTask) { if (closingTasks == null) { @@ -612,7 +614,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { if (!TransitionUtil.isOrderOnly(change) && isLeafTask) { hasChangingApp = true; } else if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME - && !mRecentsTask.equals(change.getContainer())) { + && !isRecentsTask ) { // Unless it is a 3p launcher. This means that the 3p launcher was already // visible (eg. the "pausing" task is translucent over the 3p launcher). // Treat it as if we are "re-opening" the 3p launcher. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 3669bceebc54..af8ef174b168 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -624,8 +624,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, Toast.LENGTH_SHORT).show(); } } - mStageCoordinator.startShortcutAndTask(shortcutInfo, options1, taskId, options2, - splitPosition, splitRatio, remoteTransition, instanceId); + mStageCoordinator.startShortcutAndTask(shortcutInfo, activityOptions.toBundle(), taskId, + options2, splitPosition, splitRatio, remoteTransition, instanceId); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 05a9f481b7a6..693bf6c02a84 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -28,6 +28,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.transitTypeToString; @@ -218,6 +219,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private boolean mIsDropEntering; private boolean mIsExiting; private boolean mIsRootTranslucent; + @VisibleForTesting + int mTopStageAfterFoldDismiss; private DefaultMixedHandler mMixedHandler; private final Toast mSplitUnsupportedToast; @@ -1302,20 +1305,30 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final boolean mainStageVisible = mMainStage.mRootTaskInfo.isVisible; final boolean oneStageVisible = mMainStage.mRootTaskInfo.isVisible != mSideStage.mRootTaskInfo.isVisible; - if (oneStageVisible) { + if (oneStageVisible && !ENABLE_SHELL_TRANSITIONS) { // Dismiss split because there's show-when-locked activity showing on top of keyguard. // Also make sure the task contains show-when-locked activity remains on top after split // dismissed. - if (!ENABLE_SHELL_TRANSITIONS) { - final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage; - exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); - } else { - final int dismissTop = mainStageVisible ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; + final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage; + exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); + } + + // Dismiss split if the flag record any side of stages. + if (mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) { + if (ENABLE_SHELL_TRANSITIONS) { + // Need manually clear here due to this transition might be aborted due to keyguard + // on top and lead to no visible change. + clearSplitPairedInRecents(EXIT_REASON_DEVICE_FOLDED); final WindowContainerTransaction wct = new WindowContainerTransaction(); - prepareExitSplitScreen(dismissTop, wct); - mSplitTransitions.startDismissTransition(wct, this, dismissTop, - EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); + prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct); + mSplitTransitions.startDismissTransition(wct, this, + mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED); + } else { + exitSplitScreen( + mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage, + EXIT_REASON_DEVICE_FOLDED); } + mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; } } @@ -1353,15 +1366,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (!mMainStage.isActive() || mIsExiting) return; onSplitScreenExit(); + clearSplitPairedInRecents(exitReason); - mRecentTasks.ifPresent(recentTasks -> { - // Notify recents if we are exiting in a way that breaks the pair, and disable further - // updates to splits in the recents until we enter split again - if (shouldBreakPairedTaskInRecents(exitReason) && mShouldUpdateRecents) { - recentTasks.removeSplitPair(mMainStage.getTopVisibleChildTaskId()); - recentTasks.removeSplitPair(mSideStage.getTopVisibleChildTaskId()); - } - }); mShouldUpdateRecents = false; mIsDividerRemoteAnimating = false; mSplitRequest = null; @@ -1488,6 +1494,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + private void clearSplitPairedInRecents(@ExitReason int exitReason) { + if (!shouldBreakPairedTaskInRecents(exitReason) || !mShouldUpdateRecents) return; + + mRecentTasks.ifPresent(recentTasks -> { + // Notify recents if we are exiting in a way that breaks the pair, and disable further + // updates to splits in the recents until we enter split again + mMainStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId)); + mSideStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId)); + }); + } + /** * Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates * an existing WindowContainerTransaction (rather than applying immediately). This is intended @@ -1570,6 +1587,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // split bounds. wct.setSmallestScreenWidthDp(mMainStage.mRootTaskInfo.token, SMALLEST_SCREEN_WIDTH_DP_UNDEFINED); + mSplitLayout.getInvisibleBounds(mTempRect1); + mSplitLayout.setTaskBounds(wct, mSideStage.mRootTaskInfo, mTempRect1); } wct.reorder(mRootTaskInfo.token, true); setRootForceTranslucent(false, wct); @@ -2213,7 +2232,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } - void updateSurfaces(SurfaceControl.Transaction transaction) { + /** + * Update surfaces of the split screen layout based on the current state + * @param transaction to write the updates to + */ + public void updateSurfaces(SurfaceControl.Transaction transaction) { updateSurfaceBounds(mSplitLayout, transaction, /* applyResizingOffset */ false); mSplitLayout.update(transaction); } @@ -2232,26 +2255,18 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @VisibleForTesting void onFoldedStateChanged(boolean folded) { - int topStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; + mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; if (!folded) return; - if (!mMainStage.isActive()) return; + if (!isSplitActive() || !isSplitScreenVisible()) return; + // To avoid split dismiss when user fold the device and unfold to use later, we only + // record the flag here and try to dismiss on wakeUp callback to ensure split dismiss + // when user interact on phone folded. if (mMainStage.isFocused()) { - topStageAfterFoldDismiss = STAGE_TYPE_MAIN; + mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN; } else if (mSideStage.isFocused()) { - topStageAfterFoldDismiss = STAGE_TYPE_SIDE; - } - - if (ENABLE_SHELL_TRANSITIONS) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - prepareExitSplitScreen(topStageAfterFoldDismiss, wct); - mSplitTransitions.startDismissTransition(wct, this, - topStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED); - } else { - exitSplitScreen( - topStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage, - EXIT_REASON_DEVICE_FOLDED); + mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE; } } @@ -2385,6 +2400,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // so appends operations to exit split. prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out); } + } else if (type == TRANSIT_KEYGUARD_OCCLUDE && triggerTask.topActivity != null + && isSplitScreenVisible()) { + // Split include show when lock activity case, check the top activity under which + // stage and move it to the top. + int top = triggerTask.topActivity.equals(mMainStage.mRootTaskInfo.topActivity) + ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; + prepareExitSplitScreen(top, out); + mSplitTransitions.setDismissTransition(transition, top, + EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); } // When split in the background, it should be only opening/dismissing transition and diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index e2c55e4c63ea..af7bf360f036 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -55,6 +55,7 @@ import com.android.wm.shell.windowdecor.WindowDecorViewModel; import java.io.PrintWriter; import java.util.Optional; +import java.util.function.Consumer; import java.util.function.Predicate; /** @@ -347,6 +348,13 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */); } + void doForAllChildTasks(Consumer<Integer> consumer) { + for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { + final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); + consumer.accept(taskInfo.taskId); + } + } + /** Collects all the current child tasks and prepares transaction to evict them to display. */ void evictAllChildren(WindowContainerTransaction wct) { for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java index 5baf2e320227..16f0e3987e24 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java @@ -202,15 +202,10 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { if (taskView == null) return null; // Opening types should all be initiated by shell if (!TransitionUtil.isClosingType(request.getType())) return null; - PendingTransition pending = findPendingCloseTransition(taskView); - if (pending == null) { - pending = new PendingTransition(request.getType(), null, taskView, null /* cookie */); - } - if (pending.mClaimed != null) { - throw new IllegalStateException("Task is closing in 2 collecting transitions?" - + " This state doesn't make sense"); - } + PendingTransition pending = new PendingTransition(request.getType(), null, + taskView, null /* cookie */); pending.mClaimed = transition; + mPending.add(pending); return new WindowContainerTransaction(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index d9edde16a863..c5c22ded8b71 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -704,6 +704,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, if (mPipHandler != null) { mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction); } + if (mSplitHandler != null && mSplitHandler.isSplitActive()) { + mSplitHandler.updateSurfaces(startTransaction); + } return mUnfoldHandler.startAnimation( mixed.mTransition, info, startTransaction, finishTransaction, finishCB); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java index 21994a997be5..bb5d54652460 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java @@ -270,7 +270,6 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator, @Override public void prepareStartTransaction(Transaction transaction) { mUnfoldBackgroundController.ensureBackground(transaction); - mSplitScreenController.get().get().updateSplitScreenSurfaces(transaction); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 7245bc91cfca..14f2f9b9fcf5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -24,9 +24,9 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; -import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.DRAG_FREEFORM_SCALE; import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE; import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION; +import static com.android.wm.shell.windowdecor.MoveToDesktopAnimator.DRAG_FREEFORM_SCALE; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -112,9 +112,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private SplitScreenController mSplitScreenController; - private ValueAnimator mDragToDesktopValueAnimator; + private MoveToDesktopAnimator mMoveToDesktopAnimator; private final Rect mDragToDesktopAnimationStartBounds = new Rect(); - private boolean mDragToDesktopAnimationStarted; public DesktopModeWindowDecorViewModel( Context context, @@ -233,7 +232,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { removeTaskFromEventReceiver(oldTaskInfo.displayId); incrementEventReceiverTasks(taskInfo.displayId); } - decoration.relayout(taskInfo); } @@ -599,7 +597,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } case MotionEvent.ACTION_UP: { if (relevantDecor == null) { - mDragToDesktopAnimationStarted = false; + mMoveToDesktopAnimator = null; mTransitionDragActive = false; return; } @@ -613,14 +611,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } else if (DesktopModeStatus.isProto1Enabled()) { mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true)); } - mDragToDesktopAnimationStarted = false; + mMoveToDesktopAnimator = null; return; - } else if (mDragToDesktopAnimationStarted) { - Point position = new Point((int) ev.getX(), (int) ev.getY()); + } else if (mMoveToDesktopAnimator != null) { relevantDecor.incrementRelayoutBlock(); mDesktopTasksController.ifPresent( - c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo, position)); - mDragToDesktopAnimationStarted = false; + c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo, + mMoveToDesktopAnimator)); + mMoveToDesktopAnimator = null; return; } } @@ -640,21 +638,19 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final int statusBarHeight = getStatusBarHeight( relevantDecor.mTaskInfo.displayId); if (ev.getY() > statusBarHeight) { - if (!mDragToDesktopAnimationStarted) { - mDragToDesktopAnimationStarted = true; + if (mMoveToDesktopAnimator == null) { + mMoveToDesktopAnimator = new MoveToDesktopAnimator( + mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo, + relevantDecor.mTaskSurface); mDesktopTasksController.ifPresent( c -> c.moveToFreeform(relevantDecor.mTaskInfo, - mDragToDesktopAnimationStartBounds)); - startAnimation(relevantDecor); + mDragToDesktopAnimationStartBounds, + mMoveToDesktopAnimator)); + mMoveToDesktopAnimator.startAnimation(); } } - if (mDragToDesktopAnimationStarted) { - Transaction t = mTransactionFactory.get(); - float width = (float) mDragToDesktopValueAnimator.getAnimatedValue() - * mDragToDesktopAnimationStartBounds.width(); - float x = ev.getX() - (width / 2); - t.setPosition(relevantDecor.mTaskSurface, x, ev.getY()); - t.apply(); + if (mMoveToDesktopAnimator != null) { + mMoveToDesktopAnimator.updatePosition(ev); } } break; @@ -662,7 +658,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { case MotionEvent.ACTION_CANCEL: { mTransitionDragActive = false; - mDragToDesktopAnimationStarted = false; + mMoveToDesktopAnimator = null; } } } @@ -729,20 +725,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { animator.start(); } - private void startAnimation(@NonNull DesktopModeWindowDecoration focusedDecor) { - mDragToDesktopValueAnimator = ValueAnimator.ofFloat(1f, DRAG_FREEFORM_SCALE); - mDragToDesktopValueAnimator.setDuration(FREEFORM_ANIMATION_DURATION); - final Transaction t = mTransactionFactory.get(); - mDragToDesktopValueAnimator.addUpdateListener(animation -> { - final float animatorValue = (float) animation.getAnimatedValue(); - SurfaceControl sc = focusedDecor.mTaskSurface; - t.setScale(sc, animatorValue, animatorValue); - t.apply(); - }); - - mDragToDesktopValueAnimator.start(); - } - @Nullable private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) { if (mSplitScreenController != null && mSplitScreenController.isSplitScreenVisible()) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index 336d95e7b62b..7c6fb99e9c8b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -336,6 +336,7 @@ class DragResizeInputListener implements AutoCloseable { private final Runnable mConsumeBatchEventRunnable; private boolean mConsumeBatchEventScheduled; private boolean mShouldHandleEvents; + private int mLastCursorType = PointerIcon.TYPE_DEFAULT; private TaskResizeInputEventReceiver( InputChannel inputChannel, Handler handler, Choreographer choreographer) { @@ -437,7 +438,6 @@ class DragResizeInputListener implements AutoCloseable { break; } case MotionEvent.ACTION_HOVER_EXIT: - mInputManager.setPointerIconType(PointerIcon.TYPE_DEFAULT); result = true; break; } @@ -477,7 +477,13 @@ class DragResizeInputListener implements AutoCloseable { if (y > mTaskHeight - mTaskCornerRadius) { ctrlType |= CTRL_TYPE_BOTTOM; } - return checkDistanceFromCenter(ctrlType, x, y); + // Check distances from the center if it's in one of four corners. + if ((ctrlType & (CTRL_TYPE_LEFT | CTRL_TYPE_RIGHT)) != 0 + && (ctrlType & (CTRL_TYPE_TOP | CTRL_TYPE_BOTTOM)) != 0) { + return checkDistanceFromCenter(ctrlType, x, y); + } + // Otherwise, we should make sure we don't resize tasks inside task bounds. + return (x < 0 || y < 0 || x >= mTaskWidth || y >= mTaskHeight) ? ctrlType : 0; } // If corner input is not within appropriate distance of corner radius, do not use it. @@ -511,7 +517,8 @@ class DragResizeInputListener implements AutoCloseable { break; } default: { - return ctrlType; + throw new IllegalArgumentException("ctrlType should be complex, but it's 0x" + + Integer.toHexString(ctrlType)); } } double distanceFromCenter = Math.hypot(x - centerX, y - centerY); @@ -564,7 +571,19 @@ class DragResizeInputListener implements AutoCloseable { cursorType = PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW; break; } - mInputManager.setPointerIconType(cursorType); + // Only update the cursor type to default once so that views behind the decor container + // layer that aren't in the active resizing regions have chances to update the cursor + // type. We would like to enforce the cursor type by setting the cursor type multilple + // times in active regions because we shouldn't allow the views behind to change it, as + // we'll pilfer the gesture initiated in this area. This is necessary because 1) we + // should allow the views behind regions only for touches to set the cursor type; and 2) + // there is a small region out of each rounded corner that's inside the task bounds, + // where views in the task can receive input events because we can't set touch regions + // of input sinks to have rounded corners. + if (mLastCursorType != cursorType || cursorType != PointerIcon.TYPE_DEFAULT) { + mInputManager.setPointerIconType(cursorType); + mLastCursorType = cursorType; + } } } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt new file mode 100644 index 000000000000..b2267ddb6ba7 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt @@ -0,0 +1,72 @@ +package com.android.wm.shell.windowdecor + +import android.animation.ValueAnimator +import android.app.ActivityManager.RunningTaskInfo +import android.graphics.PointF +import android.graphics.Rect +import android.view.MotionEvent +import android.view.SurfaceControl + +/** + * Creates an animator to shrink and position task after a user drags a fullscreen task from + * the top of the screen to transition it into freeform and before the user releases the task. The + * MoveToDesktopAnimator object also holds information about the state of the task that are + * accessed by the EnterDesktopTaskTransitionHandler. + */ +class MoveToDesktopAnimator @JvmOverloads constructor( + private val startBounds: Rect, + private val taskInfo: RunningTaskInfo, + private val taskSurface: SurfaceControl, + private val transactionFactory: () -> SurfaceControl.Transaction = + SurfaceControl::Transaction +) { + companion object { + // The size of the screen during drag relative to the fullscreen size + const val DRAG_FREEFORM_SCALE: Float = 0.4f + const val ANIMATION_DURATION = 336 + } + + private val animatedTaskWidth + get() = dragToDesktopAnimator.animatedValue as Float * startBounds.width() + private val dragToDesktopAnimator: ValueAnimator = ValueAnimator.ofFloat(1f, + DRAG_FREEFORM_SCALE) + .setDuration(ANIMATION_DURATION.toLong()) + .apply { + val t = SurfaceControl.Transaction() + addUpdateListener { animation -> + val animatorValue = animation.animatedValue as Float + t.setScale(taskSurface, animatorValue, animatorValue) + .apply() + } + } + + val taskId get() = taskInfo.taskId + val position: PointF = PointF(0.0f, 0.0f) + + /** + * Starts the animation that scales the task down. + */ + fun startAnimation() { + dragToDesktopAnimator.start() + } + + /** + * Uses the position of the motion event and the current scale of the task as defined by the + * ValueAnimator to update the local position variable and set the task surface's position + */ + fun updatePosition(ev: MotionEvent) { + position.x = ev.x - animatedTaskWidth / 2 + position.y = ev.y + + val t = transactionFactory() + t.setPosition(taskSurface, position.x, position.y) + t.apply() + } + + /** + * Ends the animation, setting the scale and position to the final animation value + */ + fun endAnimator() { + dragToDesktopAnimator.end() + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp index 89747dfa5765..208ae84645c3 100644 --- a/libs/WindowManager/Shell/tests/flicker/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/Android.bp @@ -138,7 +138,7 @@ android_test { android_test { name: "WMShellFlickerServiceTests", defaults: ["WMShellFlickerTestsDefault"], - additional_manifests: ["manifests/AndroidManifestSplitScreen.xml"], + additional_manifests: ["manifests/AndroidManifestService.xml"], package_name: "com.android.wm.shell.flicker.service", instrumentation_target_package: "com.android.wm.shell.flicker.service", srcs: [ diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml index 991d7b5480c4..c8a96377800f 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml @@ -93,6 +93,8 @@ value="/data/user/0/com.android.server.wm.flicker.pip/files"/> <option name="directory-keys" value="/data/user/0/com.android.server.wm.flicker.splitscreen/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.server.wm.flicker.service/files"/> <option name="collect-on-run-ended-only" value="true"/> <option name="clean-up" value="true"/> </metrics_collector> diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml new file mode 100644 index 000000000000..c7aca1a72696 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml @@ -0,0 +1,24 @@ +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.wm.shell.flicker.service"> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.wm.shell.flicker.service" + android:label="WindowManager Flicker Service Tests"> + </instrumentation> +</manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt index 6fe88cacbbc7..d3f3c5b7c672 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt @@ -18,10 +18,10 @@ package com.android.wm.shell.flicker.appcompat import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.RequiresDevice -import android.tools.common.flicker.assertions.FlickerTest import android.tools.common.NavBar import android.tools.common.Rotation import android.tools.common.datatypes.Rect +import android.tools.common.flicker.assertions.FlickerTest import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -45,7 +45,6 @@ import org.junit.runners.Parameterized * Swipe right from the bottom of the screen to quick switch back to the app * ``` */ - @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @@ -152,9 +151,8 @@ open class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) : @Test fun appWindowBecomesAndStaysVisible() { flicker.assertWm { - this.isAppWindowInvisible(letterboxApp) - .then() - .isAppWindowVisible(letterboxApp) } + this.isAppWindowInvisible(letterboxApp).then().isAppWindowVisible(letterboxApp) + } } /** @@ -245,7 +243,8 @@ open class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) : .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) .then() .isVisible(letterboxApp) - .isVisible(ComponentNameMatcher.LETTERBOX) } + .isVisible(ComponentNameMatcher.LETTERBOX) + } } /** {@inheritDoc} */ @@ -273,4 +272,4 @@ open class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) : ) } } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt index 8a85374d0712..8bd44c3a7fd0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt @@ -66,11 +66,12 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) : AutoEnterPipOnGoToHomeTest(flicker) { private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) /** Second app used to enter split screen mode */ - private val secondAppForSplitScreen = SimpleAppHelper( - instrumentation, - ActivityOptions.SplitScreen.Primary.LABEL, - ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent() - ) + private val secondAppForSplitScreen = + SimpleAppHelper( + instrumentation, + ActivityOptions.SplitScreen.Primary.LABEL, + ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent() + ) /** Defines the transition used to run the test */ override val transition: FlickerBuilder.() -> Unit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt new file mode 100644 index 000000000000..610cedefe594 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service + +import android.app.Instrumentation +import android.platform.test.rule.NavigationModeRule +import android.platform.test.rule.PressHomeRule +import android.platform.test.rule.UnlockScreenRule +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.apphelpers.MessagingAppHelper +import android.tools.device.flicker.rules.ChangeDisplayOrientationRule +import android.tools.device.flicker.rules.LaunchAppRule +import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.rules.RuleChain + +object Utils { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + + fun testSetupRule(navigationMode: NavBar, rotation: Rotation): RuleChain { + return RuleChain.outerRule(UnlockScreenRule()) + .around( + NavigationModeRule(navigationMode.value, /* changeNavigationModeAfterTest */ false) + ) + .around( + LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false) + ) + .around(RemoveAllTasksButHomeRule()) + .around( + ChangeDisplayOrientationRule( + rotation, + resetOrientationAfterTest = false, + clearCacheAfterParsing = false + ) + ) + .around(PressHomeRule()) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..566adec75615 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit +import org.junit.Test + +@RequiresDevice +class CopyContentInSplitGesturalNavLandscapeBenchmark : CopyContentInSplit(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun copyContentInSplit() = super.copyContentInSplit() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..92b62273d8cb --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit +import org.junit.Test + +@RequiresDevice +class CopyContentInSplitGesturalNavPortraitBenchmark : CopyContentInSplit(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun copyContentInSplit() = super.copyContentInSplit() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..e6d56b5c94d3 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider +import org.junit.Test + +@RequiresDevice +class DismissSplitScreenByDividerGesturalNavLandscapeBenchmark : + DismissSplitScreenByDivider(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..6752c58bd568 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider +import org.junit.Test + +@RequiresDevice +class DismissSplitScreenByDividerGesturalNavPortraitBenchmark : + DismissSplitScreenByDivider(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..7c9ab9939dd0 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome +import org.junit.Test + +@RequiresDevice +class DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark : + DismissSplitScreenByGoHome(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..4b795713cb23 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome +import org.junit.Test + +@RequiresDevice +class DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark : + DismissSplitScreenByGoHome(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..04950799732e --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize +import org.junit.Test + +@RequiresDevice +class DragDividerToResizeGesturalNavLandscapeBenchmark : DragDividerToResize(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun dragDividerToResize() = super.dragDividerToResize() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..71ef48bea686 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize +import org.junit.Test + +@RequiresDevice +class DragDividerToResizeGesturalNavPortraitBenchmark : DragDividerToResize(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun dragDividerToResize() = super.dragDividerToResize() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..c78729c6dc92 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark : + EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..30bce2f657b1 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark : + EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..b33ea7c89158 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark : + EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromNotification() = + super.enterSplitScreenByDragFromNotification() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..07a86a57117b --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark : + EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromNotification() = + super.enterSplitScreenByDragFromNotification() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..9a1d12787b9d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark : + EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..266e268a3537 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark : + EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..83fc30bceb7b --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark : + EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..b2f19299c7f0 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark : + EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..dae92dddbfec --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark : + EnterSplitScreenFromOverview(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..732047ba38ad --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview +import org.junit.Test + +@RequiresDevice +class EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark : + EnterSplitScreenFromOverview(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..1de7efd7970a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider +import org.junit.Test + +@RequiresDevice +class SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark : + SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..1a046aa5b09e --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider +import org.junit.Test + +@RequiresDevice +class SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark : + SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..6e88f0eddee8 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp +import org.junit.Test + +@RequiresDevice +class SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark : + SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..d26a29c80583 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp +import org.junit.Test + +@RequiresDevice +class SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark : + SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..4a552b0aed6a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome +import org.junit.Test + +@RequiresDevice +class SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark : + SwitchBackToSplitFromHome(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..b7376eaea66d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome +import org.junit.Test + +@RequiresDevice +class SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark : + SwitchBackToSplitFromHome(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..b2d05e4a2632 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent +import org.junit.Test + +@RequiresDevice +class SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark : + SwitchBackToSplitFromRecent(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..6de31b1315e4 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent +import org.junit.Test + +@RequiresDevice +class SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark : + SwitchBackToSplitFromRecent(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..aab18a6d27b9 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs +import org.junit.Test + +@RequiresDevice +class SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark : + SwitchBetweenSplitPairs(Rotation.ROTATION_90) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..b074f2c161c9 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.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.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs +import org.junit.Test + +@RequiresDevice +class SwitchBetweenSplitPairsGesturalNavPortraitBenchmark : + SwitchBetweenSplitPairs(Rotation.ROTATION_0) { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt new file mode 100644 index 000000000000..c402aa4444d8 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +@RequiresDevice +@RunWith(BlockJUnit4ClassRunner::class) +class UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark : UnlockKeyguardToSplitScreen() { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt new file mode 100644 index 000000000000..840401c23a91 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.benchmark + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +@RequiresDevice +@RunWith(BlockJUnit4ClassRunner::class) +class UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark : UnlockKeyguardToSplitScreen() { + @PlatinumTest(focusArea = "sysui") + @Presubmit + @Test + override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen() +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt index 964a78591d12..a5c512267508 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt @@ -29,9 +29,7 @@ import org.junit.runner.RunWith @RunWith(FlickerServiceJUnit4ClassRunner::class) class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) { - @ExpectedScenarios([]) - @Test - override fun copyContentInSplit() = super.copyContentInSplit() + @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit() companion object { @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt index bc30d4157e84..092fb6720e57 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt @@ -29,9 +29,7 @@ import org.junit.runner.RunWith @RunWith(FlickerServiceJUnit4ClassRunner::class) class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) { - @ExpectedScenarios([]) - @Test - override fun copyContentInSplit() = super.copyContentInSplit() + @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit() companion object { @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt index 76ad6b9bc49c..5bfc8896a6ef 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt @@ -23,6 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.Utils import org.junit.After import org.junit.Before import org.junit.Ignore @@ -42,9 +43,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) private val textEditApp = SplitScreenUtils.getIme(instrumentation) - @Rule - @JvmField - val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) @Before fun setup() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt index 25182b40a300..d07daffcf8a3 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt @@ -23,6 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.Utils import org.junit.After import org.junit.Before import org.junit.Ignore @@ -40,9 +41,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) - @Rule - @JvmField - val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) @Before fun setup() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt index 000b628b5ff6..d428deaaf488 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt @@ -23,6 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.Utils import org.junit.After import org.junit.Before import org.junit.Ignore @@ -40,9 +41,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) - @Rule - @JvmField - val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) @Before fun setup() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt index dd9ff3c7f64f..dc2a6ac54fc8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt @@ -23,6 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.Utils import org.junit.After import org.junit.Before import org.junit.Ignore @@ -40,9 +41,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) - @Rule - @JvmField - val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) @Before fun setup() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt index 4bbb9aa07911..677aeb078058 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt @@ -23,6 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.Utils import org.junit.After import org.junit.Before import org.junit.Ignore @@ -40,9 +41,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) - @Rule - @JvmField - val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) @Before fun setup() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt index a2b75267b662..f4f68789ea4b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt @@ -23,6 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.Utils import org.junit.After import org.junit.Before import org.junit.Ignore @@ -41,9 +42,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation) - @Rule - @JvmField - val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) @Before fun setup() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt index 1ccd8133c234..36a458feba64 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt @@ -23,6 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.Utils import org.junit.After import org.junit.Assume import org.junit.Before @@ -41,9 +42,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) - @Rule - @JvmField - val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) @Before fun setup() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt index 664786b9e523..322f7115ec98 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt @@ -23,6 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.Utils import org.junit.After import org.junit.Before import org.junit.Ignore @@ -40,9 +41,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) - @Rule - @JvmField - val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) @Before fun setup() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt index 88fd0841b174..f1644519e18a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt @@ -23,6 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.Utils import org.junit.After import org.junit.Before import org.junit.Ignore @@ -40,9 +41,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) - @Rule - @JvmField - val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) @Before fun setup() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt index 83a18e8d0b49..3831c65526a4 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt @@ -19,25 +19,15 @@ package com.android.wm.shell.flicker.service.splitscreen.scenarios import android.app.Instrumentation import android.graphics.Point import android.os.SystemClock -import android.platform.test.rule.NavigationModeRule -import android.platform.test.rule.PressHomeRule -import android.platform.test.rule.UnlockScreenRule -import android.tools.common.NavBar -import android.tools.common.Rotation import android.tools.common.traces.component.ComponentNameMatcher import android.tools.common.traces.component.IComponentMatcher import android.tools.common.traces.component.IComponentNameMatcher -import android.tools.device.apphelpers.MessagingAppHelper import android.tools.device.apphelpers.StandardAppHelper -import android.tools.device.flicker.rules.ChangeDisplayOrientationRule -import android.tools.device.flicker.rules.LaunchAppRule -import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule import android.tools.device.traces.parsers.WindowManagerStateHelper import android.tools.device.traces.parsers.toFlickerComponent import android.view.InputDevice import android.view.MotionEvent import android.view.ViewConfiguration -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.By import androidx.test.uiautomator.BySelector import androidx.test.uiautomator.UiDevice @@ -52,7 +42,6 @@ import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME import org.junit.Assert.assertNotNull -import org.junit.rules.RuleChain object SplitScreenUtils { private const val TIMEOUT_MS = 3_000L @@ -73,30 +62,6 @@ object SplitScreenUtils { private val overviewSnapshotSelector: BySelector get() = By.res(LAUNCHER_UI_PACKAGE_NAME, OVERVIEW_SNAPSHOT) - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - - fun testSetupRule(navigationMode: () -> NavBar, rotation: () -> Rotation): RuleChain { - return RuleChain.outerRule(UnlockScreenRule()) - .around( - NavigationModeRule( - navigationMode().value, - /* changeNavigationModeAfterTest */ false - ) - ) - .around( - LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false) - ) - .around(RemoveAllTasksButHomeRule()) - .around( - ChangeDisplayOrientationRule( - rotation(), - resetOrientationAfterTest = false, - clearCacheAfterParsing = false - ) - ) - .around(PressHomeRule()) - } - fun getPrimary(instrumentation: Instrumentation): StandardAppHelper = SimpleAppHelper( instrumentation, diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt index e5501f4c6cd2..805d98785a33 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt @@ -25,6 +25,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.Utils import org.junit.After import org.junit.Before import org.junit.Ignore @@ -42,9 +43,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) - @Rule - @JvmField - val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) @Before fun setup() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt index b3f1e87380e4..4229ebb1cebb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt @@ -23,6 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.Utils import org.junit.After import org.junit.Before import org.junit.Ignore @@ -41,9 +42,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation) - @Rule - @JvmField - val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) @Before fun setup() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt index d1121162c267..f2d56b99dab6 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt @@ -23,6 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.Utils import org.junit.After import org.junit.Before import org.junit.Ignore @@ -40,9 +41,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) - @Rule - @JvmField - val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) @Before fun setup() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt index 9ab924ca46f6..d44d1779a3f6 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt @@ -23,6 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.Utils import org.junit.After import org.junit.Before import org.junit.Ignore @@ -40,9 +41,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) - @Rule - @JvmField - val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) @Before fun setup() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt index b694dfa7f384..e2c6ca67d730 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt @@ -23,6 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.Utils import org.junit.After import org.junit.Before import org.junit.Ignore @@ -42,9 +43,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { private val thirdApp = SplitScreenUtils.getIme(instrumentation) private val fourthApp = SplitScreenUtils.getSendNotification(instrumentation) - @Rule - @JvmField - val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) @Before fun setup() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt index f78b7881735a..df98d8f6145a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt @@ -23,6 +23,7 @@ import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.wm.shell.flicker.service.Utils import org.junit.After import org.junit.Before import org.junit.Ignore @@ -40,8 +41,7 @@ abstract class UnlockKeyguardToSplitScreen { @Rule @JvmField - val testSetupRule = - SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { Rotation.ROTATION_0 }) + val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) @Before fun setup() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt index a43ad9b4dd39..1d4c4d2f7068 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest -import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.common.traces.component.EdgeExtensionComponentMatcher @@ -28,13 +27,9 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT -import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd -import com.android.wm.shell.flicker.appWindowIsVisibleAtStart import com.android.wm.shell.flicker.appWindowKeepVisible import com.android.wm.shell.flicker.layerKeepVisible import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible -import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd -import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart import com.android.wm.shell.flicker.splitscreen.benchmark.CopyContentInSplitBenchmark import org.junit.FixMethodOrder import org.junit.Test @@ -60,21 +55,6 @@ class CopyContentInSplit(override val flicker: LegacyFlickerTest) : thisTransition(this) } - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - override fun cujCompleted() { - flicker.appWindowIsVisibleAtStart(primaryApp) - flicker.appWindowIsVisibleAtStart(textEditApp) - flicker.splitScreenDividerIsVisibleAtStart() - - flicker.appWindowIsVisibleAtEnd(primaryApp) - flicker.appWindowIsVisibleAtEnd(textEditApp) - flicker.splitScreenDividerIsVisibleAtEnd() - - // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit() - } - @Presubmit @Test fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt index a118c08b35e2..0d967eb15e0f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest -import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -26,13 +25,9 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT -import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd -import com.android.wm.shell.flicker.appWindowIsVisibleAtStart import com.android.wm.shell.flicker.appWindowKeepVisible import com.android.wm.shell.flicker.layerKeepVisible import com.android.wm.shell.flicker.splitAppLayerBoundsChanges -import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd -import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart import com.android.wm.shell.flicker.splitscreen.benchmark.DragDividerToResizeBenchmark import org.junit.FixMethodOrder import org.junit.Test @@ -58,19 +53,6 @@ class DragDividerToResize(override val flicker: LegacyFlickerTest) : thisTransition(this) } - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - override fun cujCompleted() { - flicker.appWindowIsVisibleAtStart(primaryApp) - flicker.appWindowIsVisibleAtStart(secondaryApp) - flicker.splitScreenDividerIsVisibleAtStart() - - flicker.appWindowIsVisibleAtEnd(primaryApp) - flicker.appWindowIsVisibleAtEnd(secondaryApp) - flicker.splitScreenDividerIsVisibleAtEnd() - } - @Presubmit @Test fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt index e0a47b394ba1..f236c2d11ecb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.splitscreen -import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.tools.common.NavBar @@ -28,12 +27,9 @@ import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd -import com.android.wm.shell.flicker.appWindowIsVisibleAtStart import com.android.wm.shell.flicker.layerIsVisibleAtEnd import com.android.wm.shell.flicker.layerKeepVisible import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd -import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd -import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchAppByDoubleTapDividerBenchmark import org.junit.FixMethodOrder import org.junit.Test @@ -59,19 +55,6 @@ class SwitchAppByDoubleTapDivider(override val flicker: LegacyFlickerTest) : thisTransition(this) } - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - override fun cujCompleted() { - flicker.appWindowIsVisibleAtStart(primaryApp) - flicker.appWindowIsVisibleAtStart(secondaryApp) - flicker.splitScreenDividerIsVisibleAtStart() - - flicker.appWindowIsVisibleAtEnd(primaryApp) - flicker.appWindowIsVisibleAtEnd(secondaryApp) - flicker.splitScreenDividerIsVisibleAtEnd() - } - @Presubmit @Test fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt index 8f867df3fea1..8aaa98a5ca9f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest -import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -28,15 +27,10 @@ import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowBecomesInvisible import com.android.wm.shell.flicker.appWindowBecomesVisible -import com.android.wm.shell.flicker.appWindowIsInvisibleAtEnd -import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd -import com.android.wm.shell.flicker.appWindowIsVisibleAtStart import com.android.wm.shell.flicker.layerBecomesInvisible import com.android.wm.shell.flicker.layerBecomesVisible import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.splitAppLayerBoundsSnapToDivider -import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd -import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBetweenSplitPairsBenchmark import org.junit.FixMethodOrder import org.junit.Test @@ -62,21 +56,6 @@ class SwitchBetweenSplitPairs(override val flicker: LegacyFlickerTest) : thisTransition(this) } - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - override fun cujCompleted() { - flicker.appWindowIsVisibleAtStart(thirdApp) - flicker.appWindowIsVisibleAtStart(fourthApp) - flicker.splitScreenDividerIsVisibleAtStart() - - flicker.appWindowIsVisibleAtEnd(primaryApp) - flicker.appWindowIsVisibleAtEnd(secondaryApp) - flicker.appWindowIsInvisibleAtEnd(thirdApp) - flicker.appWindowIsInvisibleAtEnd(fourthApp) - flicker.splitScreenDividerIsVisibleAtEnd() - } - @Presubmit @Test fun splitScreenDividerInvisibleAtMiddle() = diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt index 9c68aa488d65..d9d22def6992 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt @@ -16,18 +16,15 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import com.android.wm.shell.flicker.SplitScreenUtils +import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -36,7 +33,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTest) : +abstract class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val textEditApp = SplitScreenUtils.getIme(instrumentation) protected val magnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#") @@ -54,21 +51,6 @@ open class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTest) } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - open fun cujCompleted() { - // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit() - } - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt index 21ac7839c2f4..7e8d60b441bb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt @@ -16,18 +16,14 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenDismissed -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import com.android.wm.shell.flicker.SplitScreenUtils +import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -36,7 +32,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class DismissSplitScreenByDividerBenchmark(override val flicker: LegacyFlickerTest) : +abstract class DismissSplitScreenByDividerBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -61,19 +57,6 @@ open class DismissSplitScreenByDividerBenchmark(override val flicker: LegacyFlic } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = false) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt index 931bff6f97e5..770e0328c4f6 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt @@ -16,18 +16,14 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenDismissed -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import com.android.wm.shell.flicker.SplitScreenUtils +import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -36,7 +32,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyFlickerTest) : +abstract class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -47,19 +43,6 @@ open class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyFlick } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = true) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt index 7fa2c0bba2be..46570fde1942 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt @@ -16,19 +16,16 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import com.android.wm.shell.flicker.SplitScreenUtils +import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -37,7 +34,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerTest) : +abstract class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -45,27 +42,11 @@ open class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerTest) transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - @Before fun before() { Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart) } - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - open fun cujCompleted() { - // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is - // robust enough to get the correct end state. - } - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt index 952051f62a92..5c3d4ffa8663 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt @@ -16,21 +16,17 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import com.android.wm.shell.flicker.SplitScreenUtils +import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -39,7 +35,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: LegacyFlickerTest) : +abstract class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit @@ -57,30 +53,11 @@ open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: Lega } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - @Before fun before() { Assume.assumeTrue(tapl.isTablet) } - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = - flicker.splitScreenEntered( - primaryApp, - secondaryApp, - fromOtherApp = false, - appExistAtStart = false - ) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt index 1de1c0c5e91e..6b122c686c58 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt @@ -16,21 +16,17 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import com.android.wm.shell.flicker.SplitScreenUtils +import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -39,7 +35,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterSplitScreenByDragFromNotificationBenchmark( +abstract class EnterSplitScreenByDragFromNotificationBenchmark( override val flicker: LegacyFlickerTest ) : SplitScreenBase(flicker) { protected val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation) @@ -59,21 +55,6 @@ open class EnterSplitScreenByDragFromNotificationBenchmark( teardown { sendNotificationApp.exit(wmHelper) } } - /** {@inheritDoc} */ - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = - flicker.splitScreenEntered(primaryApp, sendNotificationApp, fromOtherApp = false) - @Before fun before() { Assume.assumeTrue(tapl.isTablet) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt index 929c7eab3105..78f9bab402e9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt @@ -16,21 +16,17 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import com.android.wm.shell.flicker.SplitScreenUtils +import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -39,8 +35,9 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterSplitScreenByDragFromShortcutBenchmark(override val flicker: LegacyFlickerTest) : - SplitScreenBase(flicker) { +abstract class EnterSplitScreenByDragFromShortcutBenchmark( + override val flicker: LegacyFlickerTest +) : SplitScreenBase(flicker) { @Before fun before() { Assume.assumeTrue(tapl.isTablet) @@ -62,25 +59,6 @@ open class EnterSplitScreenByDragFromShortcutBenchmark(override val flicker: Leg } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = - flicker.splitScreenEntered( - primaryApp, - secondaryApp, - fromOtherApp = false, - appExistAtStart = false - ) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt index 9f829c9dc46e..78907f08edf3 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt @@ -16,21 +16,17 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import com.android.wm.shell.flicker.SplitScreenUtils +import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -39,7 +35,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: LegacyFlickerTest) : +abstract class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -56,26 +52,6 @@ open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: Lega } } - /** {@inheritDoc} */ - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = - flicker.splitScreenEntered( - primaryApp, - secondaryApp, - fromOtherApp = false, - appExistAtStart = false - ) - @Before fun before() { Assume.assumeTrue(tapl.isTablet) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt index 1d5518f319d8..2c91e84a01fe 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt @@ -16,18 +16,14 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import com.android.wm.shell.flicker.SplitScreenUtils +import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -36,7 +32,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterSplitScreenFromOverviewBenchmark(override val flicker: LegacyFlickerTest) : +abstract class EnterSplitScreenFromOverviewBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -56,19 +52,6 @@ open class EnterSplitScreenFromOverviewBenchmark(override val flicker: LegacyFli } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt index a7fb93e9b645..fa09c2ee4e21 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt @@ -16,8 +16,6 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -27,10 +25,9 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.helpers.WindowUtils import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import com.android.wm.shell.flicker.SplitScreenUtils +import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -39,7 +36,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlickerTest) : +abstract class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -53,14 +50,6 @@ open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlic } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) { wmHelper .StateSyncBuilder() @@ -134,14 +123,6 @@ open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlic return displayBounds.width > displayBounds.height } - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - open fun cujCompleted() { - // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is - // robust enough to get the correct end state. - } - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt index 8358aff00213..ff220069b88e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt @@ -16,19 +16,15 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import com.android.wm.shell.flicker.SplitScreenUtils +import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -37,7 +33,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: LegacyFlickerTest) : +abstract class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation) @@ -55,19 +51,6 @@ open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: Legacy } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt index b63c7659c894..5787b02f8944 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt @@ -16,19 +16,15 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import com.android.wm.shell.flicker.SplitScreenUtils +import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -37,7 +33,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFlickerTest) : +abstract class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -53,19 +49,6 @@ open class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFlicke } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt index ce5a409b2756..b2d50911f930 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt @@ -16,19 +16,15 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitScreenEntered -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import com.android.wm.shell.flicker.SplitScreenUtils +import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -37,7 +33,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SwitchBackToSplitFromRecentBenchmark(override val flicker: LegacyFlickerTest) : +abstract class SwitchBackToSplitFromRecentBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -53,19 +49,6 @@ open class SwitchBackToSplitFromRecentBenchmark(override val flicker: LegacyFlic } } - override val transition: FlickerBuilder.() -> Unit - get() = { - withoutTracing(this) - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - - @PlatinumTest(focusArea = "sysui") - @Presubmit - @Test - fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true) - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt index 9821bfac7a74..f234e462e63e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt @@ -16,17 +16,14 @@ package com.android.wm.shell.flicker.splitscreen.benchmark -import android.platform.test.annotations.PlatinumTest -import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import com.android.wm.shell.flicker.SplitScreenUtils +import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -35,7 +32,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerTest) : +abstract class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thirdApp = SplitScreenUtils.getIme(instrumentation) protected val fourthApp = SplitScreenUtils.getSendNotification(instrumentation) @@ -64,8 +61,6 @@ open class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerT thisTransition(this) } - @PlatinumTest(focusArea = "sysui") @Presubmit @Test open fun cujCompleted() {} - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt index 4fc4627093db..61c367933159 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt @@ -22,8 +22,8 @@ import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import com.android.wm.shell.flicker.SplitScreenUtils +import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -33,7 +33,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class UnlockKeyguardToSplitScreenBenchmark(override val flicker: LegacyFlickerTest) : +abstract class UnlockKeyguardToSplitScreenBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -47,14 +47,6 @@ open class UnlockKeyguardToSplitScreenBenchmark(override val flicker: LegacyFlic } } - /** {@inheritDoc} */ - override val transition: FlickerBuilder.() -> Unit - get() = { - defaultSetup(this) - defaultTeardown(this) - thisTransition(this) - } - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java index 8592dea19289..c6642f3472f0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.verify; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.WindowConfiguration; +import android.graphics.PointF; import android.graphics.Rect; import android.os.IBinder; import android.view.SurfaceControl; @@ -41,6 +42,7 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.windowdecor.MoveToDesktopAnimator; import junit.framework.AssertionFailedError; @@ -73,6 +75,10 @@ public class EnterDesktopTaskTransitionHandlerTest { ShellExecutor mExecutor; @Mock SurfaceControl mSurfaceControl; + @Mock + MoveToDesktopAnimator mMoveToDesktopAnimator; + @Mock + PointF mPosition; private EnterDesktopTaskTransitionHandler mEnterDesktopTaskTransitionHandler; @@ -82,6 +88,7 @@ public class EnterDesktopTaskTransitionHandlerTest { doReturn(mExecutor).when(mTransitions).getMainExecutor(); doReturn(mAnimationT).when(mTransactionFactory).get(); + doReturn(mPosition).when(mMoveToDesktopAnimator).getPosition(); mEnterDesktopTaskTransitionHandler = new EnterDesktopTaskTransitionHandler(mTransitions, mTransactionFactory); @@ -89,12 +96,15 @@ public class EnterDesktopTaskTransitionHandlerTest { @Test public void testEnterFreeformAnimation() { - final int transitionType = Transitions.TRANSIT_ENTER_FREEFORM; final int taskId = 1; WindowContainerTransaction wct = new WindowContainerTransaction(); doReturn(mToken).when(mTransitions) - .startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler); - mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null); + .startTransition(Transitions.TRANSIT_ENTER_FREEFORM, wct, + mEnterDesktopTaskTransitionHandler); + doReturn(taskId).when(mMoveToDesktopAnimator).getTaskId(); + + mEnterDesktopTaskTransitionHandler.startMoveToFreeformAnimation(wct, + mMoveToDesktopAnimator, null); TransitionInfo.Change change = createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index e59d09cd1ee1..cc4db22e772c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -347,13 +347,20 @@ public class StageCoordinatorTests extends ShellTestCase { } @Test - public void testExitSplitScreenAfterFolded() { - when(mMainStage.isActive()).thenReturn(true); + public void testExitSplitScreenAfterFoldedAndWakeUp() { when(mMainStage.isFocused()).thenReturn(true); when(mMainStage.getTopVisibleChildTaskId()).thenReturn(INVALID_TASK_ID); + mSideStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().setVisible(true).build(); + mMainStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().setVisible(true).build(); + when(mStageCoordinator.isSplitActive()).thenReturn(true); + when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true); mStageCoordinator.onFoldedStateChanged(true); + assertEquals(mStageCoordinator.mTopStageAfterFoldDismiss, STAGE_TYPE_MAIN); + + mStageCoordinator.onFinishedWakingUp(); + if (Transitions.ENABLE_SHELL_TRANSITIONS) { verify(mTaskOrganizer).startNewTransition(eq(TRANSIT_SPLIT_DISMISS), notNull()); } else { diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp index 4c9a23d3fde0..740988f77270 100644 --- a/libs/hwui/jni/BitmapRegionDecoder.cpp +++ b/libs/hwui/jni/BitmapRegionDecoder.cpp @@ -90,8 +90,8 @@ public: requireUnpremul, prefColorSpace); } - bool decodeGainmapRegion(sp<uirenderer::Gainmap>* outGainmap, const SkIRect& desiredSubset, - int sampleSize, bool requireUnpremul) { + bool decodeGainmapRegion(sp<uirenderer::Gainmap>* outGainmap, int outWidth, int outHeight, + const SkIRect& desiredSubset, int sampleSize, bool requireUnpremul) { SkColorType decodeColorType = mGainmapBRD->computeOutputColorType(kN32_SkColorType); sk_sp<SkColorSpace> decodeColorSpace = mGainmapBRD->computeOutputColorSpace(decodeColorType, nullptr); @@ -109,9 +109,8 @@ public: // kPremul_SkAlphaType is used just as a placeholder as it doesn't change the underlying // allocation type. RecyclingClippingPixelAllocator will populate this with the // actual alpha type in either allocPixelRef() or copyIfNecessary() - sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap( - SkImageInfo::Make(desiredSubset.width(), desiredSubset.height(), decodeColorType, - kPremul_SkAlphaType, decodeColorSpace)); + sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(SkImageInfo::Make( + outWidth, outHeight, decodeColorType, kPremul_SkAlphaType, decodeColorSpace)); if (!nativeBitmap) { ALOGE("OOM allocating Bitmap for Gainmap"); return false; @@ -134,9 +133,12 @@ public: return true; } - SkIRect calculateGainmapRegion(const SkIRect& mainImageRegion) { + SkIRect calculateGainmapRegion(const SkIRect& mainImageRegion, int* inOutWidth, + int* inOutHeight) { const float scaleX = ((float)mGainmapBRD->width()) / mMainImageBRD->width(); const float scaleY = ((float)mGainmapBRD->height()) / mMainImageBRD->height(); + *inOutWidth *= scaleX; + *inOutHeight *= scaleY; // TODO: Account for rounding error? return SkIRect::MakeLTRB(mainImageRegion.left() * scaleX, mainImageRegion.top() * scaleY, mainImageRegion.right() * scaleX, @@ -328,21 +330,16 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in sp<uirenderer::Gainmap> gainmap; bool hasGainmap = brd->hasGainmap(); if (hasGainmap) { - SkIRect adjustedSubset{}; + int gainmapWidth = bitmap.width(); + int gainmapHeight = bitmap.height(); if (javaBitmap) { - // Clamp to the width/height of the recycled bitmap in case the reused bitmap - // was too small for the specified rectangle, in which case we need to clip - adjustedSubset = SkIRect::MakeXYWH(inputX, inputY, - std::min(subset.width(), recycledBitmap->width()), - std::min(subset.height(), recycledBitmap->height())); - } else { - // We are not recycling, so use the decoded width/height for calculating the gainmap - // subset instead to ensure the gainmap region proportionally matches - adjustedSubset = SkIRect::MakeXYWH(std::max(0, inputX), std::max(0, inputY), - bitmap.width(), bitmap.height()); + // If we are recycling we must match the inBitmap's relative dimensions + gainmapWidth = recycledBitmap->width(); + gainmapHeight = recycledBitmap->height(); } - SkIRect gainmapSubset = brd->calculateGainmapRegion(adjustedSubset); - if (!brd->decodeGainmapRegion(&gainmap, gainmapSubset, sampleSize, requireUnpremul)) { + SkIRect gainmapSubset = brd->calculateGainmapRegion(subset, &gainmapWidth, &gainmapHeight); + if (!brd->decodeGainmapRegion(&gainmap, gainmapWidth, gainmapHeight, gainmapSubset, + sampleSize, requireUnpremul)) { // If there is an error decoding Gainmap - we don't fail. We just don't provide Gainmap hasGainmap = false; } diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index a443b5c4aa60..73fb0f03052b 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -65,6 +65,7 @@ systemui_compose_java_defaults { "androidx.compose.runtime_runtime", "androidx.compose.material3_material3", "androidx.activity_activity-compose", + "androidx.compose.animation_animation-graphics", ], // By default, Compose is disabled and we compile the ComposeFacade diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 2913c169a0be..4fd47232a0df 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -865,7 +865,7 @@ <activity android:name=".settings.brightness.BrightnessDialog" android:label="@string/quick_settings_brightness_dialog_title" - android:theme="@style/Theme.SystemUI.QuickSettings.BrightnessDialog" + android:theme="@style/BrightnessDialog" android:finishOnCloseSystemDialogs="true" android:launchMode="singleInstance" android:excludeFromRecents="true" diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt index 240bace21a1c..d83596eeb853 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt @@ -99,7 +99,10 @@ private fun BouncerScene( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(60.dp), modifier = - modifier.background(MaterialTheme.colorScheme.surface).fillMaxSize().padding(32.dp) + modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.surface) + .padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp) ) { Crossfade( targetState = message, diff --git a/packages/SystemUI/compose/testing/Android.bp b/packages/SystemUI/compose/testing/Android.bp deleted file mode 100644 index 555f42ef9881..000000000000 --- a/packages/SystemUI/compose/testing/Android.bp +++ /dev/null @@ -1,43 +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 { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], -} - -android_library { - name: "SystemUIComposeTesting", - manifest: "AndroidManifest.xml", - - srcs: [ - "src/**/*.kt", - ], - - static_libs: [ - "PlatformComposeCore", - "SystemUIScreenshotLib", - - "androidx.compose.runtime_runtime", - "androidx.compose.material3_material3", - "androidx.compose.ui_ui-test-junit4", - "androidx.compose.ui_ui-test-manifest", - ], - - kotlincflags: ["-Xjvm-default=all"], -} diff --git a/packages/SystemUI/compose/testing/AndroidManifest.xml b/packages/SystemUI/compose/testing/AndroidManifest.xml deleted file mode 100644 index b1f7c3be2796..000000000000 --- a/packages/SystemUI/compose/testing/AndroidManifest.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2022 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - package="com.android.systemui.testing.compose"> - <application - android:appComponentFactory="androidx.core.app.AppComponentFactory" - tools:replace="android:appComponentFactory"> - </application> -</manifest> diff --git a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt deleted file mode 100644 index cb9e53ce4d33..000000000000 --- a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt +++ /dev/null @@ -1,95 +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.systemui.testing.compose - -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.ViewRootForTest -import androidx.compose.ui.test.junit4.createAndroidComposeRule -import androidx.compose.ui.test.onRoot -import com.android.compose.theme.PlatformTheme -import com.android.systemui.testing.screenshot.ScreenshotActivity -import com.android.systemui.testing.screenshot.SystemUIGoldenImagePathManager -import com.android.systemui.testing.screenshot.UnitTestBitmapMatcher -import com.android.systemui.testing.screenshot.drawIntoBitmap -import org.junit.rules.RuleChain -import org.junit.rules.TestRule -import org.junit.runner.Description -import org.junit.runners.model.Statement -import platform.test.screenshot.DeviceEmulationRule -import platform.test.screenshot.DeviceEmulationSpec -import platform.test.screenshot.MaterialYouColorsRule -import platform.test.screenshot.ScreenshotTestRule -import platform.test.screenshot.getEmulatedDevicePathConfig - -/** A rule for Compose screenshot diff tests. */ -class ComposeScreenshotTestRule( - emulationSpec: DeviceEmulationSpec, - assetPathRelativeToBuildRoot: String -) : TestRule { - private val colorsRule = MaterialYouColorsRule() - private val deviceEmulationRule = DeviceEmulationRule(emulationSpec) - private val screenshotRule = - ScreenshotTestRule( - SystemUIGoldenImagePathManager( - getEmulatedDevicePathConfig(emulationSpec), - assetPathRelativeToBuildRoot - ) - ) - private val composeRule = createAndroidComposeRule<ScreenshotActivity>() - private val delegateRule = - RuleChain.outerRule(colorsRule) - .around(deviceEmulationRule) - .around(screenshotRule) - .around(composeRule) - private val matcher = UnitTestBitmapMatcher - - override fun apply(base: Statement, description: Description): Statement { - return delegateRule.apply(base, description) - } - - /** - * Compare [content] with the golden image identified by [goldenIdentifier] in the context of - * [testSpec]. - */ - fun screenshotTest( - goldenIdentifier: String, - content: @Composable () -> Unit, - ) { - // Make sure that the activity draws full screen and fits the whole display instead of the - // system bars. - val activity = composeRule.activity - activity.mainExecutor.execute { activity.window.setDecorFitsSystemWindows(false) } - - // Set the content using the AndroidComposeRule to make sure that the Activity is set up - // correctly. - composeRule.setContent { - PlatformTheme { - Surface( - color = MaterialTheme.colorScheme.background, - ) { - content() - } - } - } - composeRule.waitForIdle() - - val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view - screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher) - } -} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index d2084047583b..b9d66432b5f2 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -33,8 +33,8 @@ import com.android.internal.annotations.VisibleForTesting import com.android.systemui.animation.GlyphCallback import com.android.systemui.animation.TextAnimator import com.android.systemui.customization.R -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.Logger +import com.android.systemui.log.core.MessageBuffer import java.io.PrintWriter import java.util.Calendar import java.util.Locale @@ -51,7 +51,12 @@ class AnimatableClockView @JvmOverloads constructor( defStyleAttr: Int = 0, defStyleRes: Int = 0 ) : TextView(context, attrs, defStyleAttr, defStyleRes) { - var logBuffer: LogBuffer? = null + var messageBuffer: MessageBuffer? = null + set(value) { + logger = if (value != null) Logger(value, TAG) else null + } + + private var logger: Logger? = null private val time = Calendar.getInstance() @@ -129,7 +134,7 @@ class AnimatableClockView @JvmOverloads constructor( override fun onAttachedToWindow() { super.onAttachedToWindow() - logBuffer?.log(TAG, DEBUG, "onAttachedToWindow") + logger?.d("onAttachedToWindow") refreshFormat() } @@ -145,39 +150,32 @@ class AnimatableClockView @JvmOverloads constructor( time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis() contentDescription = DateFormat.format(descFormat, time) val formattedText = DateFormat.format(format, time) - logBuffer?.log(TAG, DEBUG, - { str1 = formattedText?.toString() }, - { "refreshTime: new formattedText=$str1" } - ) + logger?.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() } // Setting text actually triggers a layout pass (because the text view is set to // wrap_content width and TextView always relayouts for this). Avoid needless // relayout if the text didn't actually change. if (!TextUtils.equals(text, formattedText)) { text = formattedText - logBuffer?.log(TAG, DEBUG, - { str1 = formattedText?.toString() }, - { "refreshTime: done setting new time text to: $str1" } - ) + logger?.d({ "refreshTime: done setting new time text to: $str1" }) { + str1 = formattedText?.toString() + } // Because the TextLayout may mutate under the hood as a result of the new text, we // notify the TextAnimator that it may have changed and request a measure/layout. A // crash will occur on the next invocation of setTextStyle if the layout is mutated // without being notified TextInterpolator being notified. if (layout != null) { textAnimator?.updateLayout(layout) - logBuffer?.log(TAG, DEBUG, "refreshTime: done updating textAnimator layout") + logger?.d("refreshTime: done updating textAnimator layout") } requestLayout() - logBuffer?.log(TAG, DEBUG, "refreshTime: after requestLayout") + logger?.d("refreshTime: after requestLayout") } } fun onTimeZoneChanged(timeZone: TimeZone?) { time.timeZone = timeZone refreshFormat() - logBuffer?.log(TAG, DEBUG, - { str1 = timeZone?.toString() }, - { "onTimeZoneChanged newTimeZone=$str1" } - ) + logger?.d({ "onTimeZoneChanged newTimeZone=$str1" }) { str1 = timeZone?.toString() } } @SuppressLint("DrawAllocation") @@ -191,7 +189,7 @@ class AnimatableClockView @JvmOverloads constructor( } else { animator.updateLayout(layout) } - logBuffer?.log(TAG, DEBUG, "onMeasure") + logger?.d("onMeasure") } override fun onDraw(canvas: Canvas) { @@ -203,12 +201,12 @@ class AnimatableClockView @JvmOverloads constructor( } else { super.onDraw(canvas) } - logBuffer?.log(TAG, DEBUG, "onDraw") + logger?.d("onDraw") } override fun invalidate() { super.invalidate() - logBuffer?.log(TAG, DEBUG, "invalidate") + logger?.d("invalidate") } override fun onTextChanged( @@ -218,10 +216,7 @@ class AnimatableClockView @JvmOverloads constructor( lengthAfter: Int ) { super.onTextChanged(text, start, lengthBefore, lengthAfter) - logBuffer?.log(TAG, DEBUG, - { str1 = text.toString() }, - { "onTextChanged text=$str1" } - ) + logger?.d({ "onTextChanged text=$str1" }) { str1 = text.toString() } } fun setLineSpacingScale(scale: Float) { @@ -235,7 +230,7 @@ class AnimatableClockView @JvmOverloads constructor( } fun animateColorChange() { - logBuffer?.log(TAG, DEBUG, "animateColorChange") + logger?.d("animateColorChange") setTextStyle( weight = lockScreenWeight, textSize = -1f, @@ -257,7 +252,7 @@ class AnimatableClockView @JvmOverloads constructor( } fun animateAppearOnLockscreen() { - logBuffer?.log(TAG, DEBUG, "animateAppearOnLockscreen") + logger?.d("animateAppearOnLockscreen") setTextStyle( weight = dozingWeight, textSize = -1f, @@ -283,7 +278,7 @@ class AnimatableClockView @JvmOverloads constructor( if (isAnimationEnabled && textAnimator == null) { return } - logBuffer?.log(TAG, DEBUG, "animateFoldAppear") + logger?.d("animateFoldAppear") setTextStyle( weight = lockScreenWeightInternal, textSize = -1f, @@ -310,7 +305,7 @@ class AnimatableClockView @JvmOverloads constructor( // Skip charge animation if dozing animation is already playing. return } - logBuffer?.log(TAG, DEBUG, "animateCharge") + logger?.d("animateCharge") val startAnimPhase2 = Runnable { setTextStyle( weight = if (isDozing()) dozingWeight else lockScreenWeight, @@ -334,7 +329,7 @@ class AnimatableClockView @JvmOverloads constructor( } fun animateDoze(isDozing: Boolean, animate: Boolean) { - logBuffer?.log(TAG, DEBUG, "animateDoze") + logger?.d("animateDoze") setTextStyle( weight = if (isDozing) dozingWeight else lockScreenWeight, textSize = -1f, @@ -453,10 +448,7 @@ class AnimatableClockView @JvmOverloads constructor( isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12 else -> DOUBLE_LINE_FORMAT_12_HOUR } - logBuffer?.log(TAG, DEBUG, - { str1 = format?.toString() }, - { "refreshFormat format=$str1" } - ) + logger?.d({ "refreshFormat format=$str1" }) { str1 = format?.toString() } descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12 refreshTime() diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt index 12f7452fe913..d65edae1caf0 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -23,10 +23,11 @@ import android.os.UserHandle import android.provider.Settings import android.util.Log import androidx.annotation.OpenForTesting -import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogMessageImpl import com.android.systemui.log.core.LogLevel import com.android.systemui.log.core.LogMessage +import com.android.systemui.log.core.Logger +import com.android.systemui.log.core.MessageBuffer import com.android.systemui.log.core.MessageInitializer import com.android.systemui.log.core.MessagePrinter import com.android.systemui.plugins.ClockController @@ -75,7 +76,7 @@ private fun <TKey, TVal> ConcurrentHashMap<TKey, TVal>.concurrentGetOrPut( private val TMP_MESSAGE: LogMessage by lazy { LogMessageImpl.Factory.create() } -private inline fun LogBuffer?.tryLog( +private inline fun Logger?.tryLog( tag: String, level: LogLevel, messageInitializer: MessageInitializer, @@ -84,7 +85,7 @@ private inline fun LogBuffer?.tryLog( ) { if (this != null) { // Wrap messagePrinter to convert it from crossinline to noinline - this.log(tag, level, messageInitializer, messagePrinter, ex) + this.log(level, messagePrinter, ex, messageInitializer) } else { messageInitializer(TMP_MESSAGE) val msg = messagePrinter(TMP_MESSAGE) @@ -110,7 +111,7 @@ open class ClockRegistry( val handleAllUsers: Boolean, defaultClockProvider: ClockProvider, val fallbackClockId: ClockId = DEFAULT_CLOCK_ID, - val logBuffer: LogBuffer? = null, + messageBuffer: MessageBuffer? = null, val keepAllLoaded: Boolean, subTag: String, var isTransitClockEnabled: Boolean = false, @@ -124,6 +125,7 @@ open class ClockRegistry( fun onAvailableClocksChanged() {} } + private val logger: Logger? = if (messageBuffer != null) Logger(messageBuffer, TAG) else null private val availableClocks = ConcurrentHashMap<ClockId, ClockInfo>() private val clockChangeListeners = mutableListOf<ClockChangeListener>() private val settingObserver = @@ -150,7 +152,7 @@ open class ClockRegistry( val knownClocks = KNOWN_PLUGINS.get(manager.getPackage()) if (knownClocks == null) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.WARNING, { str1 = manager.getPackage() }, @@ -159,7 +161,7 @@ open class ClockRegistry( return true } - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.INFO, { str1 = manager.getPackage() }, @@ -176,7 +178,7 @@ open class ClockRegistry( } if (manager != info.manager) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.ERROR, { str1 = id }, @@ -216,7 +218,7 @@ open class ClockRegistry( } if (manager != info.manager) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.ERROR, { str1 = id }, @@ -244,7 +246,7 @@ open class ClockRegistry( val id = clock.clockId val info = availableClocks[id] if (info?.manager != manager) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.ERROR, { str1 = id }, @@ -319,7 +321,7 @@ open class ClockRegistry( ClockSettings.deserialize(json) } catch (ex: Exception) { - logBuffer.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to parse clock settings" }, ex) + logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to parse clock settings" }, ex) null } settings = result @@ -348,7 +350,7 @@ open class ClockRegistry( ) } } catch (ex: Exception) { - logBuffer.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to set clock settings" }, ex) + logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to set clock settings" }, ex) } settings = value } @@ -508,9 +510,9 @@ open class ClockRegistry( } private fun onConnected(clockId: ClockId) { - logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Connected $str1" }) + logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Connected $str1" }) if (currentClockId == clockId) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.INFO, { str1 = clockId }, @@ -520,10 +522,10 @@ open class ClockRegistry( } private fun onLoaded(clockId: ClockId) { - logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Loaded $str1" }) + logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Loaded $str1" }) if (currentClockId == clockId) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.INFO, { str1 = clockId }, @@ -534,10 +536,10 @@ open class ClockRegistry( } private fun onUnloaded(clockId: ClockId) { - logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Unloaded $str1" }) + logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Unloaded $str1" }) if (currentClockId == clockId) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.WARNING, { str1 = clockId }, @@ -548,10 +550,10 @@ open class ClockRegistry( } private fun onDisconnected(clockId: ClockId) { - logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Disconnected $str1" }) + logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Disconnected $str1" }) if (currentClockId == clockId) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.WARNING, { str1 = clockId }, @@ -597,22 +599,17 @@ open class ClockRegistry( if (isEnabled && clockId.isNotEmpty()) { val clock = createClock(clockId) if (clock != null) { - logBuffer.tryLog( - TAG, - LogLevel.INFO, - { str1 = clockId }, - { "Rendering clock $str1" } - ) + logger.tryLog(TAG, LogLevel.INFO, { str1 = clockId }, { "Rendering clock $str1" }) return clock } else if (availableClocks.containsKey(clockId)) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.WARNING, { str1 = clockId }, { "Clock $str1 not loaded; using default" } ) } else { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.ERROR, { str1 = clockId }, diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt index e557c8e5902a..e539c955a3c6 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -24,7 +24,7 @@ import android.view.View import android.widget.FrameLayout import androidx.annotation.VisibleForTesting import com.android.systemui.customization.R -import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.MessageBuffer import com.android.systemui.plugins.ClockAnimations import com.android.systemui.plugins.ClockConfig import com.android.systemui.plugins.ClockController @@ -108,10 +108,10 @@ class DefaultClockController( override val config = ClockFaceConfig() - override var logBuffer: LogBuffer? - get() = view.logBuffer + override var messageBuffer: MessageBuffer? + get() = view.messageBuffer set(value) { - view.logBuffer = value + view.messageBuffer = value } override var animations: DefaultClockAnimations = DefaultClockAnimations(view, 0f, 0f) diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt index 537b7a41a898..d962732ba884 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt @@ -18,7 +18,7 @@ import android.graphics.Rect import android.graphics.drawable.Drawable import android.view.View import com.android.internal.annotations.Keep -import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.MessageBuffer import com.android.systemui.plugins.annotations.ProvidesInterface import java.io.PrintWriter import java.util.Locale @@ -95,7 +95,7 @@ interface ClockFaceController { val animations: ClockAnimations /** Some clocks may log debug information */ - var logBuffer: LogBuffer? + var messageBuffer: MessageBuffer? } /** Events that should call when various rendering parameters change */ diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags index 1f47e724e48c..9bd26ab10691 100644 --- a/packages/SystemUI/proguard_common.flags +++ b/packages/SystemUI/proguard_common.flags @@ -65,15 +65,7 @@ # The plugins, log & common subpackages act as shared libraries that might be referenced in # dynamically-loaded plugin APKs. -keep class com.android.systemui.plugins.** { *; } --keep class com.android.systemui.log.ConstantStringsLoggerImpl { *; } --keep class com.android.systemui.log.ConstantStringsLogger { *; } --keep class com.android.systemui.log.LogBuffer { *; } --keep class com.android.systemui.log.LogcatEchoTrackerDebug { *; } --keep class com.android.systemui.log.LogcatEchoTracker { *; } --keep class com.android.systemui.log.LogcatEchoTrackerProd { *; } --keep class com.android.systemui.log.LogLevel { *; } --keep class com.android.systemui.log.LogMessageImpl { *; } --keep class com.android.systemui.log.LogMessage { *; } +-keep class com.android.systemui.log.core.** { *; } -keep class com.android.systemui.fragments.FragmentService$FragmentCreator { *; } diff --git a/packages/SystemUI/res-keyguard/drawable/ic_lock_locked.xml b/packages/SystemUI/res-keyguard/drawable/ic_lock_locked.xml new file mode 100644 index 000000000000..e57298530e61 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/ic_lock_locked.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M17,8H18C19.1,8 20,8.9 20,10V20C20,21.1 19.1,22 18,22H6C4.9,22 4,21.1 4,20V10C4,8.9 4.9,8 6,8H7V6C7,3.24 9.24,1 12,1C14.76,1 17,3.24 17,6V8ZM12,3C10.34,3 9,4.34 9,6V8H15V6C15,4.34 13.66,3 12,3ZM6,20V10H18V20H6ZM14,15C14,16.1 13.1,17 12,17C10.9,17 10,16.1 10,15C10,13.9 10.9,13 12,13C13.1,13 14,13.9 14,15Z" + android:fillColor="#5F6368" + android:fillType="evenOdd"/> +</vector> diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml index f3a6bbeaaf0e..665c6127e06d 100644 --- a/packages/SystemUI/res/layout/combined_qs_header.xml +++ b/packages/SystemUI/res/layout/combined_qs_header.xml @@ -121,12 +121,10 @@ frame when animating QS <-> QQS transition <LinearLayout android:id="@+id/shade_header_system_icons" android:layout_width="wrap_content" - android:layout_height="@dimen/shade_header_system_icons_height" + app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height" + android:layout_height="@dimen/large_screen_shade_header_min_height" android:clickable="true" android:orientation="horizontal" - android:gravity="center_vertical" - android:paddingStart="@dimen/shade_header_system_icons_padding_start" - android:paddingEnd="@dimen/shade_header_system_icons_padding_end" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@id/privacy_container" app:layout_constraintTop_toTopOf="@id/clock"> @@ -134,13 +132,13 @@ frame when animating QS <-> QQS transition <com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons" android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_height="match_parent" android:paddingEnd="@dimen/signal_cluster_battery_padding" /> <com.android.systemui.battery.BatteryMeterView android:id="@+id/batteryRemainingIcon" android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_height="match_parent" app:textAppearance="@style/TextAppearance.QS.Status" /> </LinearLayout> diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml index 50dcaf333ea1..bb32022a0b5f 100644 --- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml +++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml @@ -57,7 +57,6 @@ android:layout_width="@dimen/dream_overlay_status_bar_icon_size" android:layout_height="match_parent" android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" - android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop" android:src="@drawable/ic_alarm" android:tint="@android:color/white" android:visibility="gone" @@ -68,7 +67,6 @@ android:layout_width="@dimen/dream_overlay_status_bar_icon_size" android:layout_height="match_parent" android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" - android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop" android:src="@drawable/ic_qs_dnd_on" android:tint="@android:color/white" android:visibility="gone" @@ -79,7 +77,6 @@ android:layout_width="@dimen/dream_overlay_status_bar_icon_size" android:layout_height="match_parent" android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" - android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop" android:src="@drawable/ic_signal_wifi_off" android:visibility="gone" android:contentDescription="@string/dream_overlay_status_bar_wifi_off" /> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 83702eac7fdb..9a2db6bfdef6 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -74,9 +74,6 @@ <dimen name="large_dialog_width">472dp</dimen> <dimen name="large_screen_shade_header_height">42dp</dimen> - <!-- start padding is smaller to account for status icon margins coming from drawable itself --> - <dimen name="shade_header_system_icons_padding_start">11dp</dimen> - <dimen name="shade_header_system_icons_padding_end">12dp</dimen> <!-- Lockscreen shade transition values --> <dimen name="lockscreen_shade_transition_by_tap_distance">200dp</dimen> diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml index ce545f44d9bd..d053a7a0d0bb 100644 --- a/packages/SystemUI/res/values-sw720dp/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp/dimens.xml @@ -27,9 +27,6 @@ <dimen name="large_screen_shade_header_height">56dp</dimen> - <!-- it's a bit smaller on 720dp to account for status_bar_icon_horizontal_margin --> - <dimen name="shade_header_system_icons_padding_start">10dp</dimen> - <!-- Biometric Auth pattern view size, better to align keyguard_security_width --> <dimen name="biometric_auth_pattern_view_size">348dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index b34b8f690fd5..d9466bf3b4c8 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -470,10 +470,6 @@ <dimen name="large_screen_shade_header_height">48dp</dimen> <dimen name="large_screen_shade_header_min_height">@dimen/qs_header_row_min_height</dimen> <dimen name="large_screen_shade_header_left_padding">@dimen/qs_horizontal_margin</dimen> - <dimen name="shade_header_system_icons_height">@dimen/large_screen_shade_header_min_height</dimen> - <dimen name="shade_header_system_icons_height_large_screen">32dp</dimen> - <dimen name="shade_header_system_icons_padding_start">0dp</dimen> - <dimen name="shade_header_system_icons_padding_end">0dp</dimen> <!-- The top margin of the panel that holds the list of notifications. On phones it's always 0dp but it's overridden in Car UI @@ -1797,7 +1793,6 @@ <dimen name="dream_overlay_status_bar_ambient_text_shadow_dy">0.5dp</dimen> <dimen name="dream_overlay_status_bar_ambient_text_shadow_radius">2dp</dimen> <dimen name="dream_overlay_icon_inset_dimen">0dp</dimen> - <dimen name="dream_overlay_status_bar_marginTop">22dp</dimen> <!-- Default device corner radius, used for assist UI --> <dimen name="config_rounded_mask_size">0px</dimen> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index fd74c7eae361..6b8562105c74 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -398,7 +398,8 @@ <item name="android:itemTextAppearance">@style/Control.MenuItem</item> </style> - <style name="Theme.SystemUI.QuickSettings.BrightnessDialog" parent="@android:style/Theme.DeviceDefault.Dialog"> + <!-- Cannot double inherit. Use Theme.SystemUI.QuickSettings in code to match --> + <style name="BrightnessDialog" parent="@android:style/Theme.DeviceDefault.Dialog"> <item name="android:windowBackground">@android:color/transparent</item> </style> diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml index cb2c3a19a6d5..39f4c81b6dbe 100644 --- a/packages/SystemUI/res/xml/large_screen_shade_header.xml +++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml @@ -56,7 +56,7 @@ <Constraint android:id="@+id/shade_header_system_icons"> <Layout android:layout_width="wrap_content" - android:layout_height="@dimen/shade_header_system_icons_height_large_screen" + android:layout_height="@dimen/large_screen_shade_header_min_height" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/privacy_container" app:layout_constraintTop_toTopOf="parent" diff --git a/packages/SystemUI/screenshot/Android.bp b/packages/SystemUI/screenshot/Android.bp deleted file mode 100644 index f449398fc9f8..000000000000 --- a/packages/SystemUI/screenshot/Android.bp +++ /dev/null @@ -1,45 +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 { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], -} - -android_library { - name: "SystemUIScreenshotLib", - manifest: "AndroidManifest.xml", - - srcs: [ - "src/**/*.kt", - ], - - resource_dirs: [ - "res", - ], - - static_libs: [ - "SystemUI-core", - "androidx.test.espresso.core", - "androidx.appcompat_appcompat", - "platform-screenshot-diff-core", - "guava", - ], - - kotlincflags: ["-Xjvm-default=all"], -} diff --git a/packages/SystemUI/screenshot/AndroidManifest.xml b/packages/SystemUI/screenshot/AndroidManifest.xml deleted file mode 100644 index ba3dc8c53fff..000000000000 --- a/packages/SystemUI/screenshot/AndroidManifest.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2022 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.systemui.testing.screenshot"> - <application> - <activity - android:name="com.android.systemui.testing.screenshot.ScreenshotActivity" - android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize" - android:exported="true" - android:theme="@style/Theme.SystemUI.Screenshot" /> - </application> -</manifest> diff --git a/packages/SystemUI/screenshot/res/values/themes.xml b/packages/SystemUI/screenshot/res/values/themes.xml deleted file mode 100644 index a7f8a264e892..000000000000 --- a/packages/SystemUI/screenshot/res/values/themes.xml +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2022 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<resources> - <style name="Theme.SystemUI.Screenshot" parent="Theme.SystemUI"> - <item name="android:windowActionBar">false</item> - <item name="android:windowNoTitle">true</item> - - <!-- We make the status and navigation bars transparent so that the screenshotted content is - not clipped by the status bar height when drawn into the Bitmap (which is what happens - given that we draw the view into the Bitmap using hardware acceleration). --> - <item name="android:statusBarColor">@android:color/transparent</item> - <item name="android:navigationBarColor">@android:color/transparent</item> - - <!-- Make sure that device specific cutouts don't impact the outcome of screenshot tests --> - <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> - </style> -</resources>
\ No newline at end of file diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt deleted file mode 100644 index a4a70a49fce3..000000000000 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt +++ /dev/null @@ -1,66 +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.systemui.testing.screenshot - -import android.graphics.Bitmap -import android.graphics.Canvas -import android.os.Build -import android.view.View -import platform.test.screenshot.matchers.MSSIMMatcher -import platform.test.screenshot.matchers.PixelPerfectMatcher - -/** Draw this [View] into a [Bitmap]. */ -// TODO(b/195673633): Remove this once Compose screenshot tests use hardware rendering for their -// tests. -fun View.drawIntoBitmap(): Bitmap { - val bitmap = - Bitmap.createBitmap( - measuredWidth, - measuredHeight, - Bitmap.Config.ARGB_8888, - ) - val canvas = Canvas(bitmap) - draw(canvas) - return bitmap -} - -/** - * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for - * screenshot *unit* tests. - */ -val UnitTestBitmapMatcher = - if (Build.CPU_ABI == "x86_64") { - // Different CPU architectures can sometimes end up rendering differently, so we can't do - // pixel-perfect matching on different architectures using the same golden. Given that our - // presubmits are run on cf_x86_64_phone, our goldens should be perfectly matched on the - // x86_64 architecture and use the Structural Similarity Index on others. - // TODO(b/237511747): Run our screenshot presubmit tests on arm64 instead so that we can - // do pixel perfect matching both at presubmit time and at development time with actual - // devices. - PixelPerfectMatcher() - } else { - MSSIMMatcher() - } - -/** - * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for - * screenshot *unit* tests. - * - * We use the Structural Similarity Index for integration tests because they usually contain - * additional information and noise that shouldn't break the test. - */ -val IntegrationTestBitmapMatcher = MSSIMMatcher() diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt deleted file mode 100644 index e032bb9b7e30..000000000000 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt +++ /dev/null @@ -1,107 +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.systemui.testing.screenshot - -import android.app.Activity -import android.graphics.Color -import android.view.View -import android.view.Window -import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.WindowInsetsControllerCompat -import androidx.core.view.WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.rules.RuleChain -import org.junit.rules.TestRule -import org.junit.runner.Description -import org.junit.runners.model.Statement -import platform.test.screenshot.* - -/** - * A rule that allows to run a screenshot diff test on a view that is hosted in another activity. - */ -class ExternalViewScreenshotTestRule( - emulationSpec: DeviceEmulationSpec, - assetPathRelativeToBuildRoot: String -) : TestRule { - - private val colorsRule = MaterialYouColorsRule() - private val deviceEmulationRule = DeviceEmulationRule(emulationSpec) - private val screenshotRule = - ScreenshotTestRule( - SystemUIGoldenImagePathManager( - getEmulatedDevicePathConfig(emulationSpec), - assetPathRelativeToBuildRoot - ) - ) - private val delegateRule = - RuleChain.outerRule(colorsRule).around(deviceEmulationRule).around(screenshotRule) - private val matcher = UnitTestBitmapMatcher - - override fun apply(base: Statement, description: Description): Statement { - return delegateRule.apply(base, description) - } - - /** - * Compare the content of the [view] with the golden image identified by [goldenIdentifier] in - * the context of [emulationSpec]. Window must be specified to capture views that render - * hardware buffers. - */ - fun screenshotTest(goldenIdentifier: String, view: View, window: Window? = null) { - view.removeElevationRecursively() - - ScreenshotRuleAsserter.Builder(screenshotRule) - .setScreenshotProvider { view.toBitmap(window) } - .withMatcher(matcher) - .build() - .assertGoldenImage(goldenIdentifier) - } - - /** - * Compare the content of the [activity] with the golden image identified by [goldenIdentifier] - * in the context of [emulationSpec]. - */ - fun activityScreenshotTest( - goldenIdentifier: String, - activity: Activity, - ) { - val rootView = activity.window.decorView - - // Hide system bars, remove insets, focus and make sure device-specific cutouts - // don't affect screenshots - InstrumentationRegistry.getInstrumentation().runOnMainSync { - val window = activity.window - window.setDecorFitsSystemWindows(false) - WindowInsetsControllerCompat(window, rootView).apply { - hide(WindowInsetsCompat.Type.systemBars()) - systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE - } - - window.statusBarColor = Color.TRANSPARENT - window.navigationBarColor = Color.TRANSPARENT - window.attributes = - window.attributes.apply { - layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES - } - - rootView.removeInsetsRecursively() - activity.currentFocus?.clearFocus() - } - - screenshotTest(goldenIdentifier, rootView, activity.window) - } -} diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt deleted file mode 100644 index 72d8c5a09852..000000000000 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt +++ /dev/null @@ -1,44 +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.systemui.testing.screenshot - -import androidx.test.platform.app.InstrumentationRegistry -import platform.test.screenshot.GoldenImagePathManager -import platform.test.screenshot.PathConfig - -/** A [GoldenImagePathManager] that should be used for all SystemUI screenshot tests. */ -class SystemUIGoldenImagePathManager( - pathConfig: PathConfig, - assetsPathRelativeToBuildRoot: String -) : - GoldenImagePathManager( - appContext = InstrumentationRegistry.getInstrumentation().context, - assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot, - deviceLocalPath = - InstrumentationRegistry.getInstrumentation() - .targetContext - .filesDir - .absolutePath - .toString() + "/sysui_screenshots", - pathConfig = pathConfig, - ) { - override fun toString(): String { - // This string is appended to all actual/expected screenshots on the device, so make sure - // it is a static value. - return "SystemUIGoldenImagePathManager" - } -} diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt deleted file mode 100644 index 98e9aaf44ceb..000000000000 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt +++ /dev/null @@ -1,60 +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.systemui.testing.screenshot - -import android.app.Activity -import android.content.Intent -import androidx.core.app.AppComponentFactory - -class TestAppComponentFactory : AppComponentFactory() { - - init { - instance = this - } - - private val overrides: MutableMap<String, () -> Activity> = hashMapOf() - - fun clearOverrides() { - overrides.clear() - } - - fun <T : Activity> registerActivityOverride(activity: Class<T>, provider: () -> T) { - overrides[activity.name] = provider - } - - override fun instantiateActivityCompat( - cl: ClassLoader, - className: String, - intent: Intent? - ): Activity { - return overrides - .getOrDefault(className) { super.instantiateActivityCompat(cl, className, intent) } - .invoke() - } - - companion object { - - private var instance: TestAppComponentFactory? = null - - fun getInstance(): TestAppComponentFactory = - instance - ?: error( - "TestAppComponentFactory is not initialized, " + - "did you specify it in the manifest?" - ) - } -} diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt deleted file mode 100644 index b84d26a7034a..000000000000 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt +++ /dev/null @@ -1,42 +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.systemui.testing.screenshot - -import android.view.View -import android.view.ViewGroup -import com.android.systemui.util.children -import android.view.WindowInsets - -/** - * Elevation/shadows is not deterministic when doing hardware rendering, this exentsion allows to - * disable it for any view in the hierarchy. - */ -fun View.removeElevationRecursively() { - this.elevation = 0f - (this as? ViewGroup)?.children?.forEach(View::removeElevationRecursively) -} - -/** - * Different devices could have different insets (e.g. different height of the navigation bar or - * taskbar). This method dispatches empty insets to the whole view hierarchy and removes - * the original listener, so the views won't receive real insets. - */ -fun View.removeInsetsRecursively() { - this.dispatchApplyWindowInsets(WindowInsets.CONSUMED) - this.setOnApplyWindowInsetsListener(null) - (this as? ViewGroup)?.children?.forEach(View::removeInsetsRecursively) -} diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt deleted file mode 100644 index dedf0a7a742d..000000000000 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt +++ /dev/null @@ -1,233 +0,0 @@ -package com.android.systemui.testing.screenshot - -import android.annotation.WorkerThread -import android.app.Activity -import android.content.Context -import android.content.ContextWrapper -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.HardwareRenderer -import android.graphics.Rect -import android.os.Build -import android.os.Handler -import android.os.Looper -import android.util.Log -import android.view.PixelCopy -import android.view.SurfaceView -import android.view.View -import android.view.ViewTreeObserver -import android.view.Window -import androidx.annotation.RequiresApi -import androidx.concurrent.futures.ResolvableFuture -import androidx.test.annotation.ExperimentalTestApi -import androidx.test.core.internal.os.HandlerExecutor -import androidx.test.espresso.Espresso -import androidx.test.platform.graphics.HardwareRendererCompat -import com.google.common.util.concurrent.FutureCallback -import com.google.common.util.concurrent.Futures -import com.google.common.util.concurrent.ListenableFuture -import kotlin.coroutines.suspendCoroutine -import kotlinx.coroutines.runBlocking - -/* - * This file was forked from androidx/test/core/view/ViewCapture.kt to add [Window] parameter to - * [View.captureToBitmap]. - * TODO(b/195673633): Remove this fork and use the AndroidX version instead. - */ - -/** - * Asynchronously captures an image of the underlying view into a [Bitmap]. - * - * For devices below [Build.VERSION_CODES#O] (or if the view's window cannot be determined), the - * image is obtained using [View#draw]. Otherwise, [PixelCopy] is used. - * - * This method will also enable [HardwareRendererCompat#setDrawingEnabled(boolean)] if required. - * - * This API is primarily intended for use in lower layer libraries or frameworks. For test authors, - * its recommended to use espresso or compose's captureToImage. - * - * This API is currently experimental and subject to change or removal. - */ -@ExperimentalTestApi -@RequiresApi(Build.VERSION_CODES.JELLY_BEAN) -fun View.captureToBitmap(window: Window? = null): ListenableFuture<Bitmap> { - val bitmapFuture: ResolvableFuture<Bitmap> = ResolvableFuture.create() - val mainExecutor = HandlerExecutor(Handler(Looper.getMainLooper())) - val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false - - // disable drawing again if necessary once work is complete - if (!HardwareRendererCompat.isDrawingEnabled()) { - HardwareRendererCompat.setDrawingEnabled(true) - bitmapFuture.addListener({ HardwareRendererCompat.setDrawingEnabled(false) }, mainExecutor) - } - - mainExecutor.execute { - if (isRobolectric) { - generateBitmap(bitmapFuture) - } else { - val forceRedrawFuture = forceRedraw() - forceRedrawFuture.addListener({ generateBitmap(bitmapFuture, window) }, mainExecutor) - } - } - - return bitmapFuture -} - -/** - * Synchronously captures an image of the view into a [Bitmap]. Synchronous equivalent of - * [captureToBitmap]. - */ -@WorkerThread -@ExperimentalTestApi -@RequiresApi(Build.VERSION_CODES.JELLY_BEAN) -fun View.toBitmap(window: Window? = null): Bitmap { - if (Looper.getMainLooper() == Looper.myLooper()) { - error("toBitmap() can't be called from the main thread") - } - - if (!HardwareRenderer.isDrawingEnabled()) { - error("Hardware rendering is not enabled") - } - - // Make sure we are idle. - Espresso.onIdle() - - val mainExecutor = context.mainExecutor - return runBlocking { - suspendCoroutine { continuation -> - Futures.addCallback( - captureToBitmap(window), - object : FutureCallback<Bitmap> { - override fun onSuccess(result: Bitmap?) { - continuation.resumeWith(Result.success(result!!)) - } - - override fun onFailure(t: Throwable) { - continuation.resumeWith(Result.failure(t)) - } - }, - // We know that we are not on the main thread, so we can block the current - // thread and wait for the result in the main thread. - mainExecutor, - ) - } - } -} - -/** - * Trigger a redraw of the given view. - * - * Should only be called on UI thread. - * - * @return a [ListenableFuture] that will be complete once ui drawing is complete - */ -// NoClassDefFoundError occurs on API 15 -@RequiresApi(Build.VERSION_CODES.JELLY_BEAN) -// @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -@ExperimentalTestApi -fun View.forceRedraw(): ListenableFuture<Void> { - val future: ResolvableFuture<Void> = ResolvableFuture.create() - - if (Build.VERSION.SDK_INT >= 29 && isHardwareAccelerated) { - viewTreeObserver.registerFrameCommitCallback() { future.set(null) } - } else { - viewTreeObserver.addOnDrawListener( - object : ViewTreeObserver.OnDrawListener { - var handled = false - override fun onDraw() { - if (!handled) { - handled = true - future.set(null) - // cannot remove on draw listener inside of onDraw - Handler(Looper.getMainLooper()).post { - viewTreeObserver.removeOnDrawListener(this) - } - } - } - } - ) - } - invalidate() - return future -} - -private fun View.generateBitmap( - bitmapFuture: ResolvableFuture<Bitmap>, - window: Window? = null, -) { - if (bitmapFuture.isCancelled) { - return - } - val destBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - when { - Build.VERSION.SDK_INT < 26 -> generateBitmapFromDraw(destBitmap, bitmapFuture) - this is SurfaceView -> generateBitmapFromSurfaceViewPixelCopy(destBitmap, bitmapFuture) - else -> { - val window = window ?: getActivity()?.window - if (window != null) { - generateBitmapFromPixelCopy(window, destBitmap, bitmapFuture) - } else { - Log.i( - "View.captureToImage", - "Could not find window for view. Falling back to View#draw instead of PixelCopy" - ) - generateBitmapFromDraw(destBitmap, bitmapFuture) - } - } - } -} - -@SuppressWarnings("NewApi") -private fun SurfaceView.generateBitmapFromSurfaceViewPixelCopy( - destBitmap: Bitmap, - bitmapFuture: ResolvableFuture<Bitmap> -) { - val onCopyFinished = - PixelCopy.OnPixelCopyFinishedListener { result -> - if (result == PixelCopy.SUCCESS) { - bitmapFuture.set(destBitmap) - } else { - bitmapFuture.setException( - RuntimeException(String.format("PixelCopy failed: %d", result)) - ) - } - } - PixelCopy.request(this, null, destBitmap, onCopyFinished, handler) -} - -internal fun View.generateBitmapFromDraw( - destBitmap: Bitmap, - bitmapFuture: ResolvableFuture<Bitmap> -) { - destBitmap.density = resources.displayMetrics.densityDpi - computeScroll() - val canvas = Canvas(destBitmap) - canvas.translate((-scrollX).toFloat(), (-scrollY).toFloat()) - draw(canvas) - bitmapFuture.set(destBitmap) -} - -private fun View.getActivity(): Activity? { - fun Context.getActivity(): Activity? { - return when (this) { - is Activity -> this - is ContextWrapper -> this.baseContext.getActivity() - else -> null - } - } - return context.getActivity() -} - -private fun View.generateBitmapFromPixelCopy( - window: Window, - destBitmap: Bitmap, - bitmapFuture: ResolvableFuture<Bitmap> -) { - val locationInWindow = intArrayOf(0, 0) - getLocationInWindow(locationInWindow) - val x = locationInWindow[0] - val y = locationInWindow[1] - val boundsInWindow = Rect(x, y, x + width, y + height) - - return window.generateBitmapFromPixelCopy(boundsInWindow, destBitmap, bitmapFuture) -} diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt deleted file mode 100644 index 070a45170d04..000000000000 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt +++ /dev/null @@ -1,188 +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.systemui.testing.screenshot - -import android.app.Activity -import android.app.Dialog -import android.graphics.Bitmap -import android.os.Build -import android.view.View -import android.view.ViewGroup -import android.view.ViewGroup.LayoutParams -import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import androidx.activity.ComponentActivity -import androidx.test.ext.junit.rules.ActivityScenarioRule -import java.util.concurrent.TimeUnit -import org.junit.Assert.assertEquals -import org.junit.rules.RuleChain -import org.junit.rules.TestRule -import org.junit.runner.Description -import org.junit.runners.model.Statement -import platform.test.screenshot.DeviceEmulationRule -import platform.test.screenshot.DeviceEmulationSpec -import platform.test.screenshot.MaterialYouColorsRule -import platform.test.screenshot.ScreenshotTestRule -import platform.test.screenshot.getEmulatedDevicePathConfig -import platform.test.screenshot.matchers.BitmapMatcher - -/** A rule for View screenshot diff unit tests. */ -open class ViewScreenshotTestRule( - emulationSpec: DeviceEmulationSpec, - private val matcher: BitmapMatcher = UnitTestBitmapMatcher, - assetsPathRelativeToBuildRoot: String -) : TestRule { - private val colorsRule = MaterialYouColorsRule() - private val deviceEmulationRule = DeviceEmulationRule(emulationSpec) - protected val screenshotRule = - ScreenshotTestRule( - SystemUIGoldenImagePathManager( - getEmulatedDevicePathConfig(emulationSpec), - assetsPathRelativeToBuildRoot - ) - ) - private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java) - private val roboRule = - RuleChain.outerRule(deviceEmulationRule).around(screenshotRule).around(activityRule) - private val delegateRule = RuleChain.outerRule(colorsRule).around(roboRule) - private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false - - override fun apply(base: Statement, description: Description): Statement { - if (isRobolectric) { - // In robolectric mode, we enable NATIVE graphics and unpack font and icu files. - // We need to use reflection, as this library is only needed and therefore - // only available in deviceless mode. - val nativeLoaderClassName = "org.robolectric.nativeruntime.DefaultNativeRuntimeLoader" - val defaultNativeRuntimeLoader = Class.forName(nativeLoaderClassName) - System.setProperty("robolectric.graphicsMode", "NATIVE") - defaultNativeRuntimeLoader.getMethod("injectAndLoad").invoke(null) - } - val ruleToApply = if (isRobolectric) roboRule else delegateRule - return ruleToApply.apply(base, description) - } - - protected fun takeScreenshot( - mode: Mode = Mode.WrapContent, - viewProvider: (ComponentActivity) -> View, - beforeScreenshot: (ComponentActivity) -> Unit = {} - ): Bitmap { - activityRule.scenario.onActivity { activity -> - // Make sure that the activity draws full screen and fits the whole display instead of - // the system bars. - val window = activity.window - window.setDecorFitsSystemWindows(false) - - // Set the content. - activity.setContentView(viewProvider(activity), mode.layoutParams) - - // Elevation/shadows is not deterministic when doing hardware rendering, so we disable - // it for any view in the hierarchy. - window.decorView.removeElevationRecursively() - - activity.currentFocus?.clearFocus() - } - - // We call onActivity again because it will make sure that our Activity is done measuring, - // laying out and drawing its content (that we set in the previous onActivity lambda). - var contentView: View? = null - activityRule.scenario.onActivity { activity -> - // Check that the content is what we expected. - val content = activity.requireViewById<ViewGroup>(android.R.id.content) - assertEquals(1, content.childCount) - contentView = content.getChildAt(0) - beforeScreenshot(activity) - } - - return if (isRobolectric) { - contentView?.captureToBitmap()?.get(10, TimeUnit.SECONDS) - ?: error("timeout while trying to capture view to bitmap") - } else { - contentView?.toBitmap() ?: error("contentView is null") - } - } - - /** - * Compare the content of the view provided by [viewProvider] with the golden image identified - * by [goldenIdentifier] in the context of [emulationSpec]. - */ - fun screenshotTest( - goldenIdentifier: String, - mode: Mode = Mode.WrapContent, - beforeScreenshot: (ComponentActivity) -> Unit = {}, - viewProvider: (ComponentActivity) -> View - ) { - val bitmap = takeScreenshot(mode, viewProvider, beforeScreenshot) - screenshotRule.assertBitmapAgainstGolden( - bitmap, - goldenIdentifier, - matcher, - ) - } - - /** - * Compare the content of the dialog provided by [dialogProvider] with the golden image - * identified by [goldenIdentifier] in the context of [emulationSpec]. - */ - fun dialogScreenshotTest( - goldenIdentifier: String, - dialogProvider: (Activity) -> Dialog, - ) { - var dialog: Dialog? = null - activityRule.scenario.onActivity { activity -> - dialog = - dialogProvider(activity).apply { - // Make sure that the dialog draws full screen and fits the whole display - // instead of the system bars. - window.setDecorFitsSystemWindows(false) - - // Disable enter/exit animations. - create() - window.setWindowAnimations(0) - - // Elevation/shadows is not deterministic when doing hardware rendering, so we - // disable it for any view in the hierarchy. - window.decorView.removeElevationRecursively() - - // Show the dialog. - show() - } - } - - try { - val bitmap = dialog?.toBitmap() ?: error("dialog is null") - screenshotRule.assertBitmapAgainstGolden( - bitmap, - goldenIdentifier, - matcher, - ) - } finally { - dialog?.dismiss() - } - } - - private fun Dialog.toBitmap(): Bitmap { - val window = window - return window.decorView.toBitmap(window) - } - - enum class Mode(val layoutParams: LayoutParams) { - WrapContent(LayoutParams(WRAP_CONTENT, WRAP_CONTENT)), - MatchSize(LayoutParams(MATCH_PARENT, MATCH_PARENT)), - MatchWidth(LayoutParams(MATCH_PARENT, WRAP_CONTENT)), - MatchHeight(LayoutParams(WRAP_CONTENT, MATCH_PARENT)), - } -} diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt deleted file mode 100644 index d34f46bf48a6..000000000000 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.android.systemui.testing.screenshot - -import android.graphics.Bitmap -import android.graphics.Rect -import android.os.Handler -import android.os.Looper -import android.view.PixelCopy -import android.view.Window -import androidx.concurrent.futures.ResolvableFuture - -/* - * This file was forked from androidx/test/core/view/WindowCapture.kt. - * TODO(b/195673633): Remove this fork and use the AndroidX version instead. - */ -fun Window.generateBitmapFromPixelCopy( - boundsInWindow: Rect? = null, - destBitmap: Bitmap, - bitmapFuture: ResolvableFuture<Bitmap> -) { - val onCopyFinished = - PixelCopy.OnPixelCopyFinishedListener { result -> - if (result == PixelCopy.SUCCESS) { - bitmapFuture.set(destBitmap) - } else { - bitmapFuture.setException( - RuntimeException(String.format("PixelCopy failed: %d", result)) - ) - } - } - PixelCopy.request( - this, - boundsInWindow, - destBitmap, - onCopyFinished, - Handler(Looper.getMainLooper()) - ) -} diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 91937af6f540..4e0e8d0dbcd7 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -91,9 +91,9 @@ constructor( field = value if (value != null) { smallLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" }) - value.smallClock.logBuffer = smallLogBuffer + value.smallClock.messageBuffer = smallLogBuffer largeLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" }) - value.largeClock.logBuffer = largeLogBuffer + value.largeClock.messageBuffer = largeLogBuffer value.initialize(resources, dozeAmount, 0f) diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java index 99b5d52f8322..38c07dc98471 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java @@ -16,8 +16,10 @@ package com.android.keyguard; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED; +import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Rect; import android.os.SystemClock; import android.text.TextUtils; @@ -74,6 +76,7 @@ public class KeyguardPatternView extends KeyguardInputView BouncerKeyguardMessageArea mSecurityMessageDisplay; private View mEcaView; private ConstraintLayout mContainer; + @DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN; public KeyguardPatternView(Context context) { this(context, null); @@ -95,14 +98,25 @@ public class KeyguardPatternView extends KeyguardInputView mContext, android.R.interpolator.fast_out_linear_in)); } + @Override + protected void onConfigurationChanged(Configuration newConfig) { + updateMargins(); + } + void onDevicePostureChanged(@DevicePostureInt int posture) { + mLastDevicePosture = posture; + updateMargins(); + } + + private void updateMargins() { // Update the guideline based on the device posture... float halfOpenPercentage = mContext.getResources().getFloat(R.dimen.half_opened_bouncer_height_ratio); ConstraintSet cs = new ConstraintSet(); cs.clone(mContainer); - cs.setGuidelinePercent(R.id.pattern_top_guideline, posture == DEVICE_POSTURE_HALF_OPENED + cs.setGuidelinePercent(R.id.pattern_top_guideline, + mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f); cs.applyTo(mContainer); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index 3e16d559742d..794e6941c2bb 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -77,6 +77,7 @@ public class KeyguardPinViewController }); } mPasswordEntry.setUserActivityListener(this::onUserInput); + mView.onDevicePostureChanged(mPostureController.getDevicePosture()); mPostureController.addCallback(mPostureCallback); } diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index de7a66900355..ff395da54d18 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -36,6 +36,7 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.hardware.graphics.common.AlphaInterpretation; @@ -171,7 +172,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { private int mTintColor = Color.BLACK; @VisibleForTesting protected DisplayDecorationSupport mHwcScreenDecorationSupport; - private Display.Mode mDisplayMode; + private final Point mDisplaySize = new Point(); @VisibleForTesting protected DisplayInfo mDisplayInfo = new DisplayInfo(); private DisplayCutout mDisplayCutout; @@ -484,7 +485,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable { mWindowManager = mContext.getSystemService(WindowManager.class); mContext.getDisplay().getDisplayInfo(mDisplayInfo); mRotation = mDisplayInfo.rotation; - mDisplayMode = mDisplayInfo.getMode(); + mDisplaySize.x = mDisplayInfo.getNaturalWidth(); + mDisplaySize.y = mDisplayInfo.getNaturalHeight(); mDisplayUniqueId = mDisplayInfo.uniqueId; mDisplayCutout = mDisplayInfo.displayCutout; mRoundedCornerResDelegate = @@ -505,10 +507,12 @@ public class ScreenDecorations implements CoreStartable, Dumpable { public void onDisplayChanged(int displayId) { mContext.getDisplay().getDisplayInfo(mDisplayInfo); final int newRotation = mDisplayInfo.rotation; - final Display.Mode newDisplayMode = mDisplayInfo.getMode(); if ((mOverlays != null || mScreenDecorHwcWindow != null) && (mRotation != newRotation - || displayModeChanged(mDisplayMode, newDisplayMode))) { + || displaySizeChanged(mDisplaySize, mDisplayInfo))) { + final Point newSize = new Point(); + newSize.x = mDisplayInfo.getNaturalWidth(); + newSize.y = mDisplayInfo.getNaturalHeight(); // We cannot immediately update the orientation. Otherwise // WindowManager is still deferring layout until it has finished dispatching // the config changes, which may cause divergence between what we draw @@ -520,9 +524,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable { if (mRotation != newRotation) { mLogger.logRotationChangeDeferred(mRotation, newRotation); } - if (displayModeChanged(mDisplayMode, newDisplayMode)) { - mLogger.logDisplayModeChanged( - newDisplayMode.getModeId(), mDisplayMode.getModeId()); + if (!mDisplaySize.equals(newSize)) { + mLogger.logDisplaySizeChanged(mDisplaySize, newSize); } if (mOverlays != null) { @@ -531,7 +534,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { final ViewGroup overlayView = mOverlays[i].getRootView(); overlayView.getViewTreeObserver().addOnPreDrawListener( new RestartingPreDrawListener( - overlayView, i, newRotation, newDisplayMode)); + overlayView, i, newRotation, newSize)); } } } @@ -541,7 +544,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { new RestartingPreDrawListener( mScreenDecorHwcWindow, -1, // Pass -1 for views with no specific position. - newRotation, newDisplayMode)); + newRotation, newSize)); } if (mScreenDecorHwcLayer != null) { mScreenDecorHwcLayer.pendingConfigChange = true; @@ -943,15 +946,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable { } } - private static boolean displayModeChanged(Display.Mode oldMode, Display.Mode newMode) { - if (oldMode == null) { - return true; - } - - // We purposely ignore refresh rate and id changes here, because we don't need to - // invalidate for those, and they can trigger the refresh rate to increase - return oldMode.getPhysicalWidth() != newMode.getPhysicalWidth() - || oldMode.getPhysicalHeight() != newMode.getPhysicalHeight(); + private static boolean displaySizeChanged(Point size, DisplayInfo info) { + return size.x != info.getNaturalWidth() || size.y != info.getNaturalHeight(); } private int getOverlayWindowGravity(@BoundsPosition int pos) { @@ -1170,14 +1166,14 @@ public class ScreenDecorations implements CoreStartable, Dumpable { if (mRotation != newRotation) { mDotViewController.setNewRotation(newRotation); } - final Display.Mode newMod = mDisplayInfo.getMode(); final DisplayCutout newCutout = mDisplayInfo.displayCutout; if (!mPendingConfigChange - && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod) + && (newRotation != mRotation || displaySizeChanged(mDisplaySize, mDisplayInfo) || !Objects.equals(newCutout, mDisplayCutout))) { mRotation = newRotation; - mDisplayMode = newMod; + mDisplaySize.x = mDisplayInfo.getNaturalWidth(); + mDisplaySize.y = mDisplayInfo.getNaturalHeight(); mDisplayCutout = newCutout; float ratio = getPhysicalPixelDisplaySizeRatio(); mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(ratio); @@ -1494,31 +1490,29 @@ public class ScreenDecorations implements CoreStartable, Dumpable { private final View mView; private final int mTargetRotation; - private final Display.Mode mTargetDisplayMode; + private final Point mTargetDisplaySize; // Pass -1 for ScreenDecorHwcLayer since it's a fullscreen window and has no specific // position. private final int mPosition; private RestartingPreDrawListener(View view, @BoundsPosition int position, - int targetRotation, Display.Mode targetDisplayMode) { + int targetRotation, Point targetDisplaySize) { mView = view; mTargetRotation = targetRotation; - mTargetDisplayMode = targetDisplayMode; + mTargetDisplaySize = targetDisplaySize; mPosition = position; } @Override public boolean onPreDraw() { mView.getViewTreeObserver().removeOnPreDrawListener(this); - if (mTargetRotation == mRotation - && !displayModeChanged(mDisplayMode, mTargetDisplayMode)) { + if (mTargetRotation == mRotation && mDisplaySize.equals(mTargetDisplaySize)) { if (DEBUG_LOGGING) { final String title = mPosition < 0 ? "ScreenDecorHwcLayer" : getWindowTitleByPos(mPosition); Log.i(TAG, title + " already in target rot " + mTargetRotation + " and in target resolution " - + mTargetDisplayMode.getPhysicalWidth() + "x" - + mTargetDisplayMode.getPhysicalHeight() + + mTargetDisplaySize.x + "x" + mTargetDisplaySize.y + ", allow draw without restarting it"); } return true; @@ -1533,8 +1527,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { : getWindowTitleByPos(mPosition); Log.i(TAG, title + " restarting listener fired, restarting draw for rot " + mRotation - + ", resolution " + mDisplayMode.getPhysicalWidth() + "x" - + mDisplayMode.getPhysicalHeight()); + + ", resolution " + mDisplaySize.x + "x" + mDisplaySize.y); } mView.invalidate(); return false; @@ -1560,19 +1553,18 @@ public class ScreenDecorations implements CoreStartable, Dumpable { public boolean onPreDraw() { mContext.getDisplay().getDisplayInfo(mDisplayInfo); final int displayRotation = mDisplayInfo.rotation; - final Display.Mode displayMode = mDisplayInfo.getMode(); - if ((displayRotation != mRotation || displayModeChanged(mDisplayMode, displayMode)) + if ((displayRotation != mRotation || displaySizeChanged(mDisplaySize, mDisplayInfo)) && !mPendingConfigChange) { if (DEBUG_LOGGING) { if (displayRotation != mRotation) { Log.i(TAG, "Drawing rot " + mRotation + ", but display is at rot " + displayRotation + ". Restarting draw"); } - if (displayModeChanged(mDisplayMode, displayMode)) { - Log.i(TAG, "Drawing at " + mDisplayMode.getPhysicalWidth() - + "x" + mDisplayMode.getPhysicalHeight() + ", but display is at " - + displayMode.getPhysicalWidth() + "x" - + displayMode.getPhysicalHeight() + ". Restarting draw"); + if (displaySizeChanged(mDisplaySize, mDisplayInfo)) { + Log.i(TAG, "Drawing at " + mDisplaySize.x + "x" + mDisplaySize.y + + ", but display is at " + + mDisplayInfo.getNaturalWidth() + "x" + + mDisplayInfo.getNaturalHeight() + ". Restarting draw"); } } mView.invalidate(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt index e6aeb43d9d9e..802eea300bd4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt @@ -37,4 +37,8 @@ class UdfpsBpViewController( dumpManager ) { override val tag = "UdfpsBpViewController" + + override fun shouldPauseAuth(): Boolean { + return false + } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt index ce0f2e93f26d..501bcf054d20 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt @@ -135,7 +135,7 @@ internal constructor( val listener = DialogListener(prefs, attempts, onAttemptCompleted) val d = dialogProvider(activityContext, R.style.Theme_SystemUI_Dialog).apply { - setIcon(R.drawable.ic_warning) + setIcon(R.drawable.ic_lock_locked) setOnCancelListener(listener) setNeutralButton(R.string.controls_settings_dialog_neutral_button, listener) setPositiveButton(R.string.controls_settings_dialog_positive_button, listener) diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index 40db63d609ba..b1f513d0945c 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -42,6 +42,7 @@ import com.android.systemui.media.dialog.MediaOutputSwitcherDialogUI import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator +import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherCoreStartable import com.android.systemui.power.PowerUI import com.android.systemui.reardisplay.RearDisplayDialogController import com.android.systemui.recents.Recents @@ -111,6 +112,14 @@ abstract class SystemUICoreStartableModule { @ClassKey(KeyboardUI::class) abstract fun bindKeyboardUI(sysui: KeyboardUI): CoreStartable + /** Inject into MediaProjectionTaskSwitcherCoreStartable. */ + @Binds + @IntoMap + @ClassKey(MediaProjectionTaskSwitcherCoreStartable::class) + abstract fun bindProjectedTaskListener( + sysui: MediaProjectionTaskSwitcherCoreStartable + ): CoreStartable + /** Inject into KeyguardBiometricLockoutLogger */ @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index ead24ae71cc6..3b897394c515 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -59,6 +59,7 @@ import com.android.systemui.log.dagger.LogModule; import com.android.systemui.log.dagger.MonitorLog; import com.android.systemui.log.table.TableLogBuffer; import com.android.systemui.mediaprojection.appselector.MediaProjectionModule; +import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherModule; import com.android.systemui.model.SysUiState; import com.android.systemui.motiontool.MotionToolModule; import com.android.systemui.navigationbar.NavigationBarComponent; @@ -182,6 +183,7 @@ import javax.inject.Named; LockscreenLayoutModule.class, LogModule.class, MediaProjectionModule.class, + MediaProjectionTaskSwitcherModule.class, MotionToolModule.class, PeopleHubModule.class, PeopleModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 6abe951d7f35..f892a9772226 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -89,7 +89,7 @@ object Flags { // TODO(b/277338665): Tracking Bug @JvmField val NOTIFICATION_SHELF_REFACTOR = - unreleasedFlag(271161129, "notification_shelf_refactor") + unreleasedFlag(271161129, "notification_shelf_refactor", teamfood = true) // TODO(b/288326013): Tracking Bug @JvmField @@ -146,6 +146,14 @@ object Flags { val LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING = releasedFlag(208, "lockscreen_without_secure_lock_when_dreaming") + // TODO(b/286092087): Tracking Bug + @JvmField + val ENABLE_SYSTEM_UI_DREAM_CONTROLLER = unreleasedFlag(301, "enable_system_ui_dream_controller") + + // TODO(b/288287730): Tracking Bug + @JvmField + val ENABLE_SYSTEM_UI_DREAM_HOSTING = unreleasedFlag(302, "enable_system_ui_dream_hosting") + /** * Whether the clock on a wide lock screen should use the new "stepping" animation for moving * the digits when the clock moves. @@ -429,9 +437,6 @@ object Flags { // TODO(b/263272731): Tracking Bug val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = releasedFlag(910, "media_ttt_receiver_success_ripple") - // TODO(b/263512203): Tracking Bug - val MEDIA_EXPLICIT_INDICATOR = releasedFlag(911, "media_explicit_indicator") - // TODO(b/265813373): Tracking Bug val MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE = releasedFlag(912, "media_ttt_dismiss_gesture") @@ -559,6 +564,12 @@ object Flags { val WALLPAPER_MULTI_CROP = sysPropBooleanFlag(1118, "persist.wm.debug.wallpaper_multi_crop", default = false) + // TODO(b/290220798): Tracking Bug + @Keep + @JvmField + val ENABLE_PIP2_IMPLEMENTATION = + sysPropBooleanFlag(1119, "persist.wm.debug.enable_pip2_implementation", default = false) + // 1200 - predictive back @Keep diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java index 051131433143..732102e0cf9e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java @@ -50,6 +50,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.R; import com.android.systemui.SystemUIAppComponentFactoryBase; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.NotificationMediaManager; @@ -151,6 +152,9 @@ public class KeyguardSliceProvider extends SliceProvider implements private SystemUIAppComponentFactoryBase.ContextAvailableCallback mContextAvailableCallback; @Inject WakeLockLogger mWakeLockLogger; + @Inject + @Background + Handler mBgHandler; /** * Receiver responsible for time ticking and updating the date format. @@ -502,7 +506,7 @@ public class KeyguardSliceProvider extends SliceProvider implements } protected void notifyChange() { - mContentResolver.notifyChange(mSliceUri, null /* observer */); + mBgHandler.post(() -> mContentResolver.notifyChange(mSliceUri, null /* observer */)); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt index 150de26c12c7..702a23ea5ebc 100644 --- a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt @@ -189,15 +189,15 @@ constructor( ) } - fun logDisplayModeChanged(currentMode: Int, newMode: Int) { + fun logDisplaySizeChanged(currentSize: Point, newSize: Point) { logBuffer.log( TAG, INFO, { - int1 = currentMode - int2 = newMode + str1 = currentSize.flattenToString() + str2 = newSize.flattenToString() }, - { "Resolution changed, deferring mode change to $int2, staying at $int1" }, + { "Resolution changed, deferring size change to $str2, staying at $str1" }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt index 6b993ce9e7bf..576eb9e6cb07 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt @@ -716,8 +716,7 @@ class MediaDataManager( val appUid = currentEntry?.appUid ?: Process.INVALID_UID val isExplicit = desc.extras?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) == - MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT && - mediaFlags.isExplicitIndicatorEnabled() + MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT val progress = if (mediaFlags.isResumeProgressEnabled()) { @@ -826,12 +825,10 @@ class MediaDataManager( // Explicit Indicator var isExplicit = false - if (mediaFlags.isExplicitIndicatorEnabled()) { - val mediaMetadataCompat = MediaMetadataCompat.fromMediaMetadata(metadata) - isExplicit = - mediaMetadataCompat?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) == - MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT - } + val mediaMetadataCompat = MediaMetadataCompat.fromMediaMetadata(metadata) + isExplicit = + mediaMetadataCompat?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) == + MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT // Artist name var artist: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST) @@ -1253,6 +1250,9 @@ class MediaDataManager( return try { val options = BroadcastOptions.makeBasic() options.setInteractive(true) + options.setPendingIntentBackgroundActivityStartMode( + BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED + ) intent.send(options.toBundle()) true } catch (e: PendingIntent.CanceledException) { 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 35082fd5122a..a978b92cb234 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 @@ -23,6 +23,7 @@ import static com.android.systemui.media.controls.models.recommendation.Smartspa import android.animation.Animator; import android.animation.AnimatorInflater; import android.animation.AnimatorSet; +import android.app.ActivityOptions; import android.app.BroadcastOptions; import android.app.PendingIntent; import android.app.WallpaperColors; @@ -535,7 +536,10 @@ public class MediaControlPanel { mLockscreenUserManager.getCurrentUserId()); if (showOverLockscreen) { try { - clickIntent.send(); + ActivityOptions opts = ActivityOptions.makeBasic(); + opts.setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); + clickIntent.send(opts.toBundle()); } catch (PendingIntent.CanceledException e) { Log.e(TAG, "Pending intent for " + key + " was cancelled"); } @@ -684,6 +688,8 @@ public class MediaControlPanel { try { BroadcastOptions options = BroadcastOptions.makeBasic(); options.setInteractive(true); + options.setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); deviceIntent.send(options.toBundle()); } catch (PendingIntent.CanceledException e) { Log.e(TAG, "Device pending intent was canceled"); 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 9bc66f6c98d0..01f047ccd4f5 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 @@ -43,9 +43,6 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) { */ fun areNearbyMediaDevicesEnabled() = featureFlags.isEnabled(Flags.MEDIA_NEARBY_DEVICES) - /** Check whether we show explicit indicator on UMO */ - fun isExplicitIndicatorEnabled() = featureFlags.isEnabled(Flags.MEDIA_EXPLICIT_INDICATOR) - /** * If true, keep active media controls for the lifetime of the MediaSession, regardless of * whether the underlying notification was dismissed diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt new file mode 100644 index 000000000000..3c501277ab8c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher + +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator +import javax.inject.Inject + +@SysUISingleton +class MediaProjectionTaskSwitcherCoreStartable +@Inject +constructor( + private val notificationCoordinator: TaskSwitcherNotificationCoordinator, + private val featureFlags: FeatureFlags, +) : CoreStartable { + + override fun start() { + if (featureFlags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)) { + notificationCoordinator.start() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt new file mode 100644 index 000000000000..22ad07ebc3b1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher + +import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository +import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository +import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionRepository +import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository +import dagger.Binds +import dagger.Module + +@Module +interface MediaProjectionTaskSwitcherModule { + + @Binds fun mediaRepository(impl: MediaProjectionManagerRepository): MediaProjectionRepository + + @Binds fun tasksRepository(impl: ActivityTaskManagerTasksRepository): TasksRepository +} diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt index 2a55a80eb7f4..9938f11e5d4c 100644 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,13 @@ * limitations under the License. */ -package com.android.systemui.testing.screenshot +package com.android.systemui.mediaprojection.taskswitcher.data.model -import androidx.activity.ComponentActivity +import android.app.TaskInfo -/** The Activity that is launched and whose content is set for screenshot tests. */ -class ScreenshotActivity : ComponentActivity() +/** Represents the state of media projection. */ +sealed interface MediaProjectionState { + object NotProjecting : MediaProjectionState + object EntireScreen : MediaProjectionState + data class SingleTask(val task: TaskInfo) : MediaProjectionState +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt new file mode 100644 index 000000000000..492d482459d6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.taskswitcher.data.repository + +import android.app.ActivityManager.RunningTaskInfo +import android.app.ActivityTaskManager +import android.app.TaskStackListener +import android.os.IBinder +import android.util.Log +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.withContext + +/** Implementation of [TasksRepository] that uses [ActivityTaskManager] as the data source. */ +@SysUISingleton +class ActivityTaskManagerTasksRepository +@Inject +constructor( + private val activityTaskManager: ActivityTaskManager, + @Application private val applicationScope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) : TasksRepository { + + override suspend fun findRunningTaskFromWindowContainerToken( + windowContainerToken: IBinder + ): RunningTaskInfo? = + getRunningTasks().firstOrNull { taskInfo -> + taskInfo.token.asBinder() == windowContainerToken + } + + private suspend fun getRunningTasks(): List<RunningTaskInfo> = + withContext(backgroundDispatcher) { activityTaskManager.getTasks(Integer.MAX_VALUE) } + + override val foregroundTask: Flow<RunningTaskInfo> = + conflatedCallbackFlow { + val listener = + object : TaskStackListener() { + override fun onTaskMovedToFront(taskInfo: RunningTaskInfo) { + Log.d(TAG, "onTaskMovedToFront: $taskInfo") + trySendWithFailureLogging(taskInfo, TAG) + } + } + activityTaskManager.registerTaskStackListener(listener) + awaitClose { activityTaskManager.unregisterTaskStackListener(listener) } + } + .shareIn(applicationScope, SharingStarted.Lazily, replay = 1) + + companion object { + private const val TAG = "TasksRepository" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt new file mode 100644 index 000000000000..38d4e698f2d9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt @@ -0,0 +1,92 @@ +/* + * 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.taskswitcher.data.repository + +import android.media.projection.MediaProjectionInfo +import android.media.projection.MediaProjectionManager +import android.os.Handler +import android.util.Log +import android.view.ContentRecordingSession +import android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.launch + +@SysUISingleton +class MediaProjectionManagerRepository +@Inject +constructor( + private val mediaProjectionManager: MediaProjectionManager, + @Main private val handler: Handler, + @Application private val applicationScope: CoroutineScope, + private val tasksRepository: TasksRepository, +) : MediaProjectionRepository { + + override val mediaProjectionState: Flow<MediaProjectionState> = + conflatedCallbackFlow { + val callback = + object : MediaProjectionManager.Callback() { + override fun onStart(info: MediaProjectionInfo?) { + Log.d(TAG, "MediaProjectionManager.Callback#onStart") + trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG) + } + + override fun onStop(info: MediaProjectionInfo?) { + Log.d(TAG, "MediaProjectionManager.Callback#onStop") + trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG) + } + + override fun onRecordingSessionSet( + info: MediaProjectionInfo, + session: ContentRecordingSession? + ) { + Log.d(TAG, "MediaProjectionManager.Callback#onSessionStarted: $session") + launch { trySendWithFailureLogging(stateForSession(session), TAG) } + } + } + mediaProjectionManager.addCallback(callback, handler) + awaitClose { mediaProjectionManager.removeCallback(callback) } + } + .shareIn(scope = applicationScope, started = SharingStarted.Lazily, replay = 1) + + private suspend fun stateForSession(session: ContentRecordingSession?): MediaProjectionState { + if (session == null) { + return MediaProjectionState.NotProjecting + } + if (session.contentToRecord == RECORD_CONTENT_DISPLAY || session.tokenToRecord == null) { + return MediaProjectionState.EntireScreen + } + val matchingTask = + tasksRepository.findRunningTaskFromWindowContainerToken(session.tokenToRecord) + ?: return MediaProjectionState.EntireScreen + return MediaProjectionState.SingleTask(matchingTask) + } + + companion object { + private const val TAG = "MediaProjectionMngrRepo" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt new file mode 100644 index 000000000000..5bec6925babe --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt @@ -0,0 +1,27 @@ +/* + * 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.taskswitcher.data.repository + +import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState +import kotlinx.coroutines.flow.Flow + +/** Represents a repository to retrieve and change data related to media projection. */ +interface MediaProjectionRepository { + + /** Represents the current [MediaProjectionState]. */ + val mediaProjectionState: Flow<MediaProjectionState> +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt new file mode 100644 index 000000000000..544eb6b99d4f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.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.taskswitcher.data.repository + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow + +/** + * No-op implementation of [MediaProjectionRepository] that does nothing. Currently used as a + * placeholder, while the real implementation is not completed. + */ +@SysUISingleton +class NoOpMediaProjectionRepository @Inject constructor() : MediaProjectionRepository { + + override val mediaProjectionState: Flow<MediaProjectionState> = emptyFlow() +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt new file mode 100644 index 000000000000..6a535e4ecc50 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt @@ -0,0 +1,41 @@ +/* + * 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.taskswitcher.data.repository + +import android.app.ActivityManager.RunningTaskInfo +import android.os.IBinder +import kotlinx.coroutines.flow.Flow + +/** Repository responsible for retrieving data related to running tasks. */ +interface TasksRepository { + + /** + * Tries to find a [RunningTaskInfo] with a matching window container token. Returns `null` when + * no matching task was found. + */ + suspend fun findRunningTaskFromWindowContainerToken( + windowContainerToken: IBinder + ): RunningTaskInfo? + + /** + * Emits a stream of [RunningTaskInfo] that have been moved to the foreground. + * + * Note: when subscribing for the first time, it will not immediately emit the current + * foreground task. Only after a change in foreground task has occurred. + */ + val foregroundTask: Flow<RunningTaskInfo> +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt new file mode 100644 index 000000000000..fc5cf7d75bdf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt @@ -0,0 +1,85 @@ +/* + * 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.taskswitcher.domain.interactor + +import android.app.TaskInfo +import android.content.Intent +import android.util.Log +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState +import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionRepository +import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository +import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map + +/** Interactor with logic related to task switching in the context of media projection. */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class TaskSwitchInteractor +@Inject +constructor( + mediaProjectionRepository: MediaProjectionRepository, + private val tasksRepository: TasksRepository, +) { + + /** + * Emits a stream of changes to the state of task switching, in the context of media projection. + */ + val taskSwitchChanges: Flow<TaskSwitchState> = + mediaProjectionRepository.mediaProjectionState.flatMapLatest { projectionState -> + Log.d(TAG, "MediaProjectionState -> $projectionState") + when (projectionState) { + is MediaProjectionState.SingleTask -> { + val projectedTask = projectionState.task + tasksRepository.foregroundTask.map { foregroundTask -> + if (hasForegroundTaskSwitched(projectedTask, foregroundTask)) { + TaskSwitchState.TaskSwitched(projectedTask, foregroundTask) + } else { + TaskSwitchState.TaskUnchanged + } + } + } + is MediaProjectionState.EntireScreen, + is MediaProjectionState.NotProjecting -> { + flowOf(TaskSwitchState.NotProjectingTask) + } + } + } + + /** + * Returns whether tasks have been switched. + * + * Always returns `false` when launcher is in the foreground. The reason is that when going to + * recents to switch apps, launcher becomes the new foreground task, and we don't want to show + * the notification then. + */ + private fun hasForegroundTaskSwitched(projectedTask: TaskInfo, foregroundTask: TaskInfo) = + projectedTask.taskId != foregroundTask.taskId && !foregroundTask.isLauncher + + private val TaskInfo.isLauncher + get() = + baseIntent.hasCategory(Intent.CATEGORY_HOME) && baseIntent.action == Intent.ACTION_MAIN + + companion object { + private const val TAG = "TaskSwitchInteractor" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt new file mode 100644 index 000000000000..cd1258ed6aa8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt @@ -0,0 +1,30 @@ +/* + * 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.taskswitcher.domain.model + +import android.app.TaskInfo + +/** Represents tha state of task switching in the context of single task media projection. */ +sealed interface TaskSwitchState { + /** Currently no task is being projected. */ + object NotProjectingTask : TaskSwitchState + /** The foreground task is the same as the task that is currently being projected. */ + object TaskUnchanged : TaskSwitchState + /** The foreground task is a different one to the task it currently being projected. */ + data class TaskSwitched(val projectedTask: TaskInfo, val foregroundTask: TaskInfo) : + TaskSwitchState +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt new file mode 100644 index 000000000000..a4f407612fa8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt @@ -0,0 +1,74 @@ +/* + * 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.taskswitcher.ui + +import android.content.Context +import android.util.Log +import android.widget.Toast +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.NotShowing +import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.Showing +import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.launch + +/** Coordinator responsible for showing/hiding the task switcher notification. */ +@SysUISingleton +class TaskSwitcherNotificationCoordinator +@Inject +constructor( + private val context: Context, + @Application private val applicationScope: CoroutineScope, + @Main private val mainDispatcher: CoroutineDispatcher, + private val viewModel: TaskSwitcherNotificationViewModel, +) { + + fun start() { + applicationScope.launch { + viewModel.uiState.flowOn(mainDispatcher).collect { uiState -> + Log.d(TAG, "uiState -> $uiState") + when (uiState) { + is Showing -> showNotification(uiState) + is NotShowing -> hideNotification() + } + } + } + } + + private fun showNotification(uiState: Showing) { + val text = + """ + Sharing pauses when you switch apps. + Share this app instead. + Switch back. + """ + .trimIndent() + // TODO(b/286201515): Create actual notification. + Toast.makeText(context, text, Toast.LENGTH_SHORT).show() + } + + private fun hideNotification() {} + + companion object { + private const val TAG = "TaskSwitchNotifCoord" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt new file mode 100644 index 000000000000..21aee72d17ae --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt @@ -0,0 +1,30 @@ +/* + * 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.taskswitcher.ui.model + +import android.app.TaskInfo + +/** Represents the UI state for the task switcher notification. */ +sealed interface TaskSwitcherNotificationUiState { + /** The notification should not be shown. */ + object NotShowing : TaskSwitcherNotificationUiState + /** The notification should be shown. */ + data class Showing( + val projectedTask: TaskInfo, + val foregroundTask: TaskInfo, + ) : TaskSwitcherNotificationUiState +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt new file mode 100644 index 000000000000..d9754d4429d4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt @@ -0,0 +1,49 @@ +/* + * 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.taskswitcher.ui.viewmodel + +import android.util.Log +import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor +import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState +import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class TaskSwitcherNotificationViewModel @Inject constructor(interactor: TaskSwitchInteractor) { + + val uiState: Flow<TaskSwitcherNotificationUiState> = + interactor.taskSwitchChanges.map { taskSwitchChange -> + Log.d(TAG, "taskSwitchChange: $taskSwitchChange") + when (taskSwitchChange) { + is TaskSwitchState.TaskSwitched -> { + TaskSwitcherNotificationUiState.Showing( + projectedTask = taskSwitchChange.projectedTask, + foregroundTask = taskSwitchChange.foregroundTask, + ) + } + is TaskSwitchState.NotProjectingTask, + is TaskSwitchState.TaskUnchanged -> { + TaskSwitcherNotificationUiState.NotShowing + } + } + } + + companion object { + private const val TAG = "TaskSwitchNotifVM" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java index 3aefcb3d2976..7e234aeed0aa 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java @@ -71,6 +71,8 @@ public class ActionProxyReceiver extends BroadcastReceiver { ActivityOptions opts = ActivityOptions.makeBasic(); opts.setDisallowEnterPictureInPictureWhileLaunching( intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false)); + opts.setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); try { actionIntent.send(context, 0, null, null, null, null, opts.toBundle()); if (intent.getBooleanExtra(ScreenshotController.EXTRA_OVERRIDE_TRANSITION, false)) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java index 13678b0e7187..9e8ea3aaec56 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java @@ -99,6 +99,8 @@ public class OverlayActionChip extends FrameLayout { try { BroadcastOptions options = BroadcastOptions.makeBasic(); options.setInteractive(true); + options.setPendingIntentBackgroundActivityStartMode( + BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); intent.send(options.toBundle()); finisher.run(); } catch (PendingIntent.CanceledException e) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java index 9761f5931193..ef58b9d6a1ee 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java @@ -55,7 +55,8 @@ public class SmartActionsReceiver extends BroadcastReceiver { Log.d(TAG, "Executing smart action [" + actionType + "]:" + pendingIntent.getIntent()); } ActivityOptions opts = ActivityOptions.makeBasic(); - + opts.setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); try { pendingIntent.send(context, 0, fillIn, null, null, null, opts.toBundle()); } catch (PendingIntent.CanceledException e) { diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java index 8879501fa03d..5199bd43f982 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java @@ -84,6 +84,7 @@ public class BrightnessDialog extends Activity { window.getDecorView(); window.setLayout( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT); + getTheme().applyStyle(R.style.Theme_SystemUI_QuickSettings, false); setContentView(R.layout.brightness_mirror_container); FrameLayout frame = findViewById(R.id.brightness_mirror_container); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt index 4ac5cd8b18a7..8789a8b3b7f4 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt @@ -131,7 +131,6 @@ constructor( private val date: TextView = header.findViewById(R.id.date) private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons) private val mShadeCarrierGroup: ShadeCarrierGroup = header.findViewById(R.id.carrier_group) - private val systemIcons: View = header.findViewById(R.id.shade_header_system_icons) private var roundedCorners = 0 private var cutout: DisplayCutout? = null @@ -255,14 +254,6 @@ constructor( header.paddingRight, header.paddingBottom ) - systemIcons.setPaddingRelative( - resources.getDimensionPixelSize( - R.dimen.shade_header_system_icons_padding_start - ), - systemIcons.paddingTop, - resources.getDimensionPixelSize(R.dimen.shade_header_system_icons_padding_end), - systemIcons.paddingBottom - ) } override fun onDensityOrFontScaleChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index e6e3e7e4fe4c..ea5ca276a8cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -19,6 +19,7 @@ import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER import static com.android.systemui.DejankUtils.whitelistIpcs; +import android.app.ActivityOptions; import android.app.KeyguardManager; import android.app.Notification; import android.app.admin.DevicePolicyManager; @@ -158,7 +159,11 @@ public class NotificationLockscreenUserManagerImpl implements final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX); if (intentSender != null) { try { - mContext.startIntentSender(intentSender, null, 0, 0, 0); + ActivityOptions options = ActivityOptions.makeBasic(); + options.setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); + mContext.startIntentSender(intentSender, null, 0, 0, 0, + options.toBundle()); } catch (IntentSender.SendIntentException e) { /* ignore */ } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt index 2fa070ca20b5..07eb8a00a178 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt @@ -28,12 +28,9 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.keyguard.shared.model.TransitionState -import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.expansionChanges -import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope @@ -50,30 +47,29 @@ import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import java.io.PrintWriter import javax.inject.Inject -import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.emitAll -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.launch import kotlinx.coroutines.yield /** * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section - * headers on the lockscreen. + * headers on the lockscreen. If enabled, it will also track and hide seen notifications on the + * lockscreen. */ @CoordinatorScope class KeyguardCoordinator @@ -86,7 +82,6 @@ constructor( private val keyguardRepository: KeyguardRepository, private val keyguardTransitionRepository: KeyguardTransitionRepository, private val logger: KeyguardCoordinatorLogger, - private val notifPipelineFlags: NotifPipelineFlags, @Application private val scope: CoroutineScope, private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider, private val secureSettings: SecureSettings, @@ -95,6 +90,8 @@ constructor( ) : Coordinator, Dumpable { private val unseenNotifications = mutableSetOf<NotificationEntry>() + private val unseenEntryAdded = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1) + private val unseenEntryRemoved = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1) private var unseenFilterEnabled = false override fun attach(pipeline: NotifPipeline) { @@ -109,79 +106,130 @@ constructor( private fun attachUnseenFilter(pipeline: NotifPipeline) { pipeline.addFinalizeFilter(unseenNotifFilter) pipeline.addCollectionListener(collectionListener) - scope.launch { trackUnseenNotificationsWhileUnlocked() } - scope.launch { invalidateWhenUnseenSettingChanges() } + scope.launch { trackUnseenFilterSettingChanges() } dumpManager.registerDumpable(this) } - private suspend fun trackUnseenNotificationsWhileUnlocked() { - // Whether or not we're actively tracking unseen notifications to mark them as seen when - // appropriate. - val isTrackingUnseen: Flow<Boolean> = - keyguardRepository.isKeyguardShowing - // transformLatest so that we can cancel listening to keyguard transitions once - // isKeyguardShowing changes (after a successful transition to the keyguard). - .transformLatest { isShowing -> - if (isShowing) { - // If the keyguard is showing, we're not tracking unseen. - emit(false) - } else { - // If the keyguard stops showing, then start tracking unseen notifications. - emit(true) - // If the screen is turning off, stop tracking, but if that transition is - // cancelled, then start again. - emitAll( - keyguardTransitionRepository.transitions.map { step -> - !step.isScreenTurningOff - } - ) - } - } - // Prevent double emit of `false` caused by transition to AOD, followed by keyguard - // showing + private suspend fun trackSeenNotifications() { + // Whether or not keyguard is visible (or occluded). + val isKeyguardPresent: Flow<Boolean> = + keyguardTransitionRepository.transitions + .map { step -> step.to != KeyguardState.GONE } .distinctUntilChanged() .onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) } - // Use collectLatest so that trackUnseenNotifications() is cancelled when the keyguard is - // showing again - var clearUnseenOnBeginTracking = false - isTrackingUnseen.collectLatest { trackingUnseen -> - if (!trackingUnseen) { - // Wait for the user to spend enough time on the lock screen before clearing unseen - // set when unlocked - awaitTimeSpentNotDozing(SEEN_TIMEOUT) - clearUnseenOnBeginTracking = true - logger.logSeenOnLockscreen() + // Separately track seen notifications while the device is locked, applying once the device + // is unlocked. + val notificationsSeenWhileLocked = mutableSetOf<NotificationEntry>() + + // Use [collectLatest] to cancel any running jobs when [trackingUnseen] changes. + isKeyguardPresent.collectLatest { isKeyguardPresent: Boolean -> + if (isKeyguardPresent) { + // Keyguard is not gone, notifications need to be visible for a certain threshold + // before being marked as seen + trackSeenNotificationsWhileLocked(notificationsSeenWhileLocked) } else { - if (clearUnseenOnBeginTracking) { - clearUnseenOnBeginTracking = false - logger.logAllMarkedSeenOnUnlock() - unseenNotifications.clear() + // Mark all seen-while-locked notifications as seen for real. + if (notificationsSeenWhileLocked.isNotEmpty()) { + unseenNotifications.removeAll(notificationsSeenWhileLocked) + logger.logAllMarkedSeenOnUnlock( + seenCount = notificationsSeenWhileLocked.size, + remainingUnseenCount = unseenNotifications.size + ) + notificationsSeenWhileLocked.clear() } unseenNotifFilter.invalidateList("keyguard no longer showing") - trackUnseenNotifications() + // Keyguard is gone, notifications can be immediately marked as seen when they + // become visible. + trackSeenNotificationsWhileUnlocked() } } } - private suspend fun awaitTimeSpentNotDozing(duration: Duration) { - keyguardRepository.isDozing - // Use transformLatest so that the timeout delay is cancelled if the device enters doze, - // and is restarted when doze ends. - .transformLatest { isDozing -> - if (!isDozing) { - delay(duration) - // Signal timeout has completed - emit(Unit) + /** + * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually + * been "seen" while the device is on the keyguard. + */ + private suspend fun trackSeenNotificationsWhileLocked( + notificationsSeenWhileLocked: MutableSet<NotificationEntry>, + ) = coroutineScope { + // Remove removed notifications from the set + launch { + unseenEntryRemoved.collect { entry -> + if (notificationsSeenWhileLocked.remove(entry)) { + logger.logRemoveSeenOnLockscreen(entry) + } + } + } + // Use collectLatest so that the timeout delay is cancelled if the device enters doze, and + // is restarted when doze ends. + keyguardRepository.isDozing.collectLatest { isDozing -> + if (!isDozing) { + trackSeenNotificationsWhileLockedAndNotDozing(notificationsSeenWhileLocked) + } + } + } + + /** + * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually + * been "seen" while the device is on the keyguard and not dozing. Any new and existing unseen + * notifications are not marked as seen until they are visible for the [SEEN_TIMEOUT] duration. + */ + private suspend fun trackSeenNotificationsWhileLockedAndNotDozing( + notificationsSeenWhileLocked: MutableSet<NotificationEntry> + ) = coroutineScope { + // All child tracking jobs will be cancelled automatically when this is cancelled. + val trackingJobsByEntry = mutableMapOf<NotificationEntry, Job>() + + /** + * Wait for the user to spend enough time on the lock screen before removing notification + * from unseen set upon unlock. + */ + suspend fun trackSeenDurationThreshold(entry: NotificationEntry) { + if (notificationsSeenWhileLocked.remove(entry)) { + logger.logResetSeenOnLockscreen(entry) + } + delay(SEEN_TIMEOUT) + notificationsSeenWhileLocked.add(entry) + trackingJobsByEntry.remove(entry) + logger.logSeenOnLockscreen(entry) + } + + /** Stop any unseen tracking when a notification is removed. */ + suspend fun stopTrackingRemovedNotifs(): Nothing = + unseenEntryRemoved.collect { entry -> + trackingJobsByEntry.remove(entry)?.let { + it.cancel() + logger.logStopTrackingLockscreenSeenDuration(entry) + } + } + + /** Start tracking new notifications when they are posted. */ + suspend fun trackNewUnseenNotifs(): Nothing = coroutineScope { + unseenEntryAdded.collect { entry -> + logger.logTrackingLockscreenSeenDuration(entry) + // If this is an update, reset the tracking. + trackingJobsByEntry[entry]?.let { + it.cancel() + logger.logResetSeenOnLockscreen(entry) } + trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) } } - // Suspend until the first emission - .first() + } + + // Start tracking for all notifications that are currently unseen. + logger.logTrackingLockscreenSeenDuration(unseenNotifications) + unseenNotifications.forEach { entry -> + trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) } + } + + launch { trackNewUnseenNotifs() } + launch { stopTrackingRemovedNotifs() } } - // Track "unseen" notifications, marking them as seen when either shade is expanded or the + // Track "seen" notifications, marking them as such when either shade is expanded or the // notification becomes heads up. - private suspend fun trackUnseenNotifications() { + private suspend fun trackSeenNotificationsWhileUnlocked() { coroutineScope { launch { clearUnseenNotificationsWhenShadeIsExpanded() } launch { markHeadsUpNotificationsAsSeen() } @@ -212,7 +260,7 @@ constructor( } } - private suspend fun invalidateWhenUnseenSettingChanges() { + private suspend fun trackUnseenFilterSettingChanges() { secureSettings // emit whenever the setting has changed .observerFlow( @@ -228,17 +276,23 @@ constructor( UserHandle.USER_CURRENT, ) == 1 } + // don't emit anything if nothing has changed + .distinctUntilChanged() // perform lookups on the bg thread pool .flowOn(bgDispatcher) // only track the most recent emission, if events are happening faster than they can be // consumed .conflate() - // update local field and invalidate if necessary - .collect { setting -> + .collectLatest { setting -> + // update local field and invalidate if necessary if (setting != unseenFilterEnabled) { unseenFilterEnabled = setting unseenNotifFilter.invalidateList("unseen setting changed") } + // if the setting is enabled, then start tracking and filtering unseen notifications + if (setting) { + trackSeenNotifications() + } } } @@ -250,6 +304,7 @@ constructor( ) { logger.logUnseenAdded(entry.key) unseenNotifications.add(entry) + unseenEntryAdded.tryEmit(entry) } } @@ -259,12 +314,14 @@ constructor( ) { logger.logUnseenUpdated(entry.key) unseenNotifications.add(entry) + unseenEntryAdded.tryEmit(entry) } } override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { if (unseenNotifications.remove(entry)) { logger.logUnseenRemoved(entry.key) + unseenEntryRemoved.tryEmit(entry) } } } @@ -347,6 +404,3 @@ constructor( private val SEEN_TIMEOUT = 5.seconds } } - -private val TransitionStep.isScreenTurningOff: Boolean - get() = transitionState == TransitionState.STARTED && to != KeyguardState.GONE diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt index 4c33524346eb..788659eb3ccc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.UnseenNotificationLog +import com.android.systemui.statusbar.notification.collection.NotificationEntry import javax.inject.Inject private const val TAG = "KeyguardCoordinator" @@ -28,11 +29,14 @@ class KeyguardCoordinatorLogger constructor( @UnseenNotificationLog private val buffer: LogBuffer, ) { - fun logSeenOnLockscreen() = + fun logSeenOnLockscreen(entry: NotificationEntry) = buffer.log( TAG, LogLevel.DEBUG, - "Notifications on lockscreen will be marked as seen when unlocked." + messageInitializer = { str1 = entry.key }, + messagePrinter = { + "Notification [$str1] on lockscreen will be marked as seen when unlocked." + }, ) fun logTrackingUnseen(trackingUnseen: Boolean) = @@ -43,11 +47,21 @@ constructor( messagePrinter = { "${if (bool1) "Start" else "Stop"} tracking unseen notifications." }, ) - fun logAllMarkedSeenOnUnlock() = + fun logAllMarkedSeenOnUnlock( + seenCount: Int, + remainingUnseenCount: Int, + ) = buffer.log( TAG, LogLevel.DEBUG, - "Notifications have been marked as seen now that device is unlocked." + messageInitializer = { + int1 = seenCount + int2 = remainingUnseenCount + }, + messagePrinter = { + "$int1 Notifications have been marked as seen now that device is unlocked. " + + "$int2 notifications remain unseen." + }, ) fun logShadeExpanded() = @@ -96,4 +110,60 @@ constructor( messageInitializer = { str1 = key }, messagePrinter = { "Unseen notif has become heads up: $str1" }, ) + + fun logTrackingLockscreenSeenDuration(unseenNotifications: Set<NotificationEntry>) { + buffer.log( + TAG, + LogLevel.DEBUG, + messageInitializer = { + str1 = unseenNotifications.joinToString { it.key } + int1 = unseenNotifications.size + }, + messagePrinter = { + "Tracking $int1 unseen notifications for lockscreen seen duration threshold: $str1" + }, + ) + } + + fun logTrackingLockscreenSeenDuration(entry: NotificationEntry) { + buffer.log( + TAG, + LogLevel.DEBUG, + messageInitializer = { str1 = entry.key }, + messagePrinter = { + "Tracking new notification for lockscreen seen duration threshold: $str1" + }, + ) + } + + fun logStopTrackingLockscreenSeenDuration(entry: NotificationEntry) { + buffer.log( + TAG, + LogLevel.DEBUG, + messageInitializer = { str1 = entry.key }, + messagePrinter = { + "Stop tracking removed notification for lockscreen seen duration threshold: $str1" + }, + ) + } + + fun logResetSeenOnLockscreen(entry: NotificationEntry) { + buffer.log( + TAG, + LogLevel.DEBUG, + messageInitializer = { str1 = entry.key }, + messagePrinter = { + "Reset tracking updated notification for lockscreen seen duration threshold: $str1" + }, + ) + } + + fun logRemoveSeenOnLockscreen(entry: NotificationEntry) { + buffer.log( + TAG, + LogLevel.DEBUG, + messageInitializer = { str1 = entry.key }, + messagePrinter = { "Notification marked as seen on lockscreen removed: $str1" }, + ) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 0d3dfaeb85b8..8902a186c43a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -42,6 +42,7 @@ import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.app.IWallpaperManager; import android.app.KeyguardManager; import android.app.Notification; @@ -1773,7 +1774,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION, sbn.getKey()); wakeUpForFullScreenIntent(); - notification.fullScreenIntent.send(); + ActivityOptions opts = ActivityOptions.makeBasic(); + opts.setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); + notification.fullScreenIntent.send(opts.toBundle()); entry.notifyFullScreenIntentLaunched(); } catch (PendingIntent.CanceledException e) { } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt index 22b4c9d81d25..736b14574da0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.policy +import android.app.ActivityOptions import android.app.Notification import android.app.PendingIntent import android.app.RemoteInput @@ -275,7 +276,10 @@ class RemoteInputViewControllerImpl @Inject constructor( entry.sbn.instanceId) try { - pendingIntent.send(view.context, 0, intent) + val options = ActivityOptions.makeBasic() + options.setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) + pendingIntent.send(view.context, 0, intent, null, null, null, options.toBundle()) } catch (e: PendingIntent.CanceledException) { Log.i(TAG, "Unable to send remote input result", e) uiEventLogger.logWithInstanceId( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt index cac5e3290a26..1776e5b76e55 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.policy +import android.app.ActivityOptions import android.app.Notification import android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY import android.app.PendingIntent @@ -491,7 +492,11 @@ class SmartReplyInflaterImpl @Inject constructor( entry.setHasSentReply() try { val intent = createRemoteInputIntent(smartReplies, choice) - smartReplies.pendingIntent.send(context, 0, intent) + val opts = ActivityOptions.makeBasic() + opts.setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) + smartReplies.pendingIntent.send(context, 0, intent, /* onFinished */null, + /* handler */ null, /* requiredPermission */ null, opts.toBundle()) } catch (e: PendingIntent.CanceledException) { Log.w(TAG, "Unable to send smart reply", e) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java index 33620979d681..fd7c30f39693 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java @@ -103,6 +103,8 @@ public class TvNotificationAdapter extends RecyclerView.Adapter<RecyclerView.Vie if (mPendingIntent != null) { BroadcastOptions options = BroadcastOptions.makeBasic(); options.setInteractive(true); + options.setPendingIntentBackgroundActivityStartMode( + BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); mPendingIntent.send(options.toBundle()); } } catch (PendingIntent.CanceledException e) { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt index 2eea9eb76398..5d75428b8fb4 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt @@ -32,15 +32,18 @@ import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString +import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.verify @@ -78,6 +81,9 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController private lateinit var fakeFeatureFlags: FakeFeatureFlags + @Captor + lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback> + @Before fun setup() { MockitoAnnotations.initMocks(this) @@ -107,7 +113,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { } @Test - fun tabletopPostureIsDetectedFromStart() { + fun onViewAttached_deviceHalfFolded_propagatedToPatternView() { overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f) whenever(mPostureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED) @@ -116,6 +122,26 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { assertThat(getPatternTopGuideline()).isEqualTo(getExpectedTopGuideline()) } + @Test + fun onDevicePostureChanged_deviceOpened_propagatedToPatternView() { + overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f) + whenever(mPostureController.devicePosture) + .thenReturn(DEVICE_POSTURE_HALF_OPENED) + + mKeyguardPatternViewController.onViewAttached() + + // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED + assertThat(getPatternTopGuideline()).isEqualTo(getExpectedTopGuideline()) + + // Simulate posture change to state DEVICE_POSTURE_OPENED with callback + verify(mPostureController).addCallback(postureCallbackCaptor.capture()) + val postureCallback: DevicePostureController.Callback = postureCallbackCaptor.value + postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED) + + // Verify view is now in posture state DEVICE_POSTURE_OPENED + assertThat(getPatternTopGuideline()).isNotEqualTo(getExpectedTopGuideline()) + } + private fun getPatternTopGuideline(): Float { val cs = ConstraintSet() val container = diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index d3b41902499c..9db267c2c929 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -30,12 +30,16 @@ import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.statusbar.policy.DevicePostureController +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString +import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.any @@ -79,7 +83,9 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { @Mock lateinit var deleteButton: NumPadButton @Mock lateinit var enterButton: View - lateinit var pinViewController: KeyguardPinViewController + private lateinit var pinViewController: KeyguardPinViewController + + @Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback> @Before fun setup() { @@ -97,6 +103,9 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { `when`(keyguardPinView.findViewById<NumPadButton>(R.id.delete_button)) .thenReturn(deleteButton) `when`(keyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton) + // For posture tests: + `when`(keyguardPinView.buttons).thenReturn(arrayOf()) + pinViewController = KeyguardPinViewController( keyguardPinView, @@ -115,6 +124,33 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { } @Test + fun onViewAttached_deviceHalfFolded_propagatedToPinView() { + `when`(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED) + + pinViewController.onViewAttached() + + verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED) + } + + @Test + fun onDevicePostureChanged_deviceHalfFolded_propagatedToPinView() { + `when`(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED) + + // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED + pinViewController.onViewAttached() + + verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED) + + // Simulate posture change to state DEVICE_POSTURE_OPENED with callback + verify(postureController).addCallback(postureCallbackCaptor.capture()) + val postureCallback: DevicePostureController.Callback = postureCallbackCaptor.value + postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED) + + // Verify view is now in posture state DEVICE_POSTURE_OPENED + verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_OPENED) + } + + @Test fun startAppearAnimation() { pinViewController.startAppearAnimation() verify(keyguardMessageAreaController) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt new file mode 100644 index 000000000000..7de78a60b73e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt @@ -0,0 +1,68 @@ +/* + * 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.biometrics + +import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.RoboPilotTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.statusbar.phone.SystemUIDialogManager +import org.junit.Assert.assertFalse +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnit + +@SmallTest +@RoboPilotTest +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper +class UdfpsBpViewControllerTest : SysuiTestCase() { + + @JvmField @Rule var rule = MockitoJUnit.rule() + + @Mock lateinit var udfpsBpView: UdfpsBpView + @Mock lateinit var statusBarStateController: StatusBarStateController + @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager + @Mock lateinit var systemUIDialogManager: SystemUIDialogManager + @Mock lateinit var dumpManager: DumpManager + + private lateinit var udfpsBpViewController: UdfpsBpViewController + + @Before + fun setup() { + udfpsBpViewController = + UdfpsBpViewController( + udfpsBpView, + statusBarStateController, + shadeExpansionStateManager, + systemUIDialogManager, + dumpManager + ) + } + + @Test + fun testShouldNeverPauseAuth() { + assertFalse(udfpsBpViewController.shouldPauseAuth()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt index 42106756b473..71d2ec152e5a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt @@ -11,9 +11,9 @@ import android.window.OnBackInvokedCallback import android.window.OnBackInvokedDispatcher import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule -import androidx.test.runner.intercepting.SingleActivityFactory import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.controls.CustomIconCache import com.android.systemui.controls.controller.ControlsControllerImpl import com.android.systemui.flags.FakeFeatureFlags @@ -63,24 +63,19 @@ class ControlsEditingActivityTest : SysuiTestCase() { @JvmField var activityRule = ActivityTestRule( - object : - SingleActivityFactory<TestableControlsEditingActivity>( - TestableControlsEditingActivity::class.java - ) { - override fun create(intent: Intent?): TestableControlsEditingActivity { - return TestableControlsEditingActivity( - featureFlags, - uiExecutor, - controller, - userTracker, - customIconCache, - mockDispatcher, - latch - ) - } + /* activityFactory= */ SingleActivityFactory { + TestableControlsEditingActivity( + featureFlags, + uiExecutor, + controller, + userTracker, + customIconCache, + mockDispatcher, + latch + ) }, - false, - false + /* initialTouchMode= */ false, + /* launchActivity= */ false, ) @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt index f4cc8bcb09a5..f11c296ad572 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt @@ -13,9 +13,9 @@ import android.window.OnBackInvokedDispatcher import androidx.test.filters.FlakyTest import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule -import androidx.test.runner.intercepting.SingleActivityFactory import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.controls.ControlStatus import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.controller.ControlsController @@ -91,24 +91,19 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { @JvmField var activityRule = ActivityTestRule( - object : - SingleActivityFactory<TestableControlsFavoritingActivity>( - TestableControlsFavoritingActivity::class.java - ) { - override fun create(intent: Intent?): TestableControlsFavoritingActivity { - return TestableControlsFavoritingActivity( - featureFlags, - executor, - controller, - listingController, - userTracker, - mockDispatcher, - latch - ) - } + /* activityFactory= */ SingleActivityFactory { + TestableControlsFavoritingActivity( + featureFlags, + executor, + controller, + listingController, + userTracker, + mockDispatcher, + latch + ) }, - false, - false + /* initialTouchMode= */ false, + /* launchActivity= */ false, ) @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt index 4ba67182c32c..d17495f21a68 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt @@ -29,8 +29,8 @@ import android.window.OnBackInvokedCallback import android.window.OnBackInvokedDispatcher import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule -import androidx.test.runner.intercepting.SingleActivityFactory import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.panels.AuthorizedPanelsRepository @@ -91,26 +91,21 @@ class ControlsProviderSelectorActivityTest : SysuiTestCase() { @JvmField var activityRule = ActivityTestRule( - object : - SingleActivityFactory<TestableControlsProviderSelectorActivity>( - TestableControlsProviderSelectorActivity::class.java - ) { - override fun create(intent: Intent?): TestableControlsProviderSelectorActivity { - return TestableControlsProviderSelectorActivity( - executor, - backExecutor, - listingController, - controlsController, - userTracker, - authorizedPanelsRepository, - dialogFactory, - mockDispatcher, - latch - ) - } + /* activityFactory= */ SingleActivityFactory { + TestableControlsProviderSelectorActivity( + executor, + backExecutor, + listingController, + controlsController, + userTracker, + authorizedPanelsRepository, + dialogFactory, + mockDispatcher, + latch + ) }, - false, - false + /* initialTouchMode= */ false, + /* launchActivity= */ false, ) @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt index 314b17625f00..ca970bb41d56 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt @@ -30,8 +30,8 @@ import android.testing.TestableLooper import androidx.lifecycle.Lifecycle import androidx.test.filters.MediumTest import androidx.test.rule.ActivityTestRule -import androidx.test.runner.intercepting.SingleActivityFactory import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.controls.controller.ControlInfo import com.android.systemui.controls.controller.ControlsController import com.android.systemui.settings.UserTracker @@ -81,19 +81,18 @@ class ControlsRequestDialogTest : SysuiTestCase() { @Rule @JvmField - var activityRule = ActivityTestRule<TestControlsRequestDialog>( - object : SingleActivityFactory<TestControlsRequestDialog>( - TestControlsRequestDialog::class.java - ) { - override fun create(intent: Intent?): TestControlsRequestDialog { - return TestControlsRequestDialog( - mainExecutor, - controller, - userTracker, - listingController - ) - } - }, false, false) + var activityRule = ActivityTestRule( + /* activityFactory= */ SingleActivityFactory { + TestControlsRequestDialog( + mainExecutor, + controller, + userTracker, + listingController + ) + }, + /* initialTouchMode= */ false, + /* launchActivity= */ false, + ) private lateinit var control: Control diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt index 2d3e10e6f7e0..e279d28de499 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt @@ -23,8 +23,8 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule -import androidx.test.runner.intercepting.SingleActivityFactory import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.settings.ControlsSettingsDialogManager import com.android.systemui.flags.FeatureFlags @@ -53,23 +53,18 @@ class ControlsActivityTest : SysuiTestCase() { @JvmField var activityRule = ActivityTestRule( - object : - SingleActivityFactory<TestableControlsActivity>( - TestableControlsActivity::class.java - ) { - override fun create(intent: Intent?): TestableControlsActivity { - return TestableControlsActivity( - uiController, - broadcastDispatcher, - dreamManager, - featureFlags, - controlsSettingsDialogManager, - keyguardStateController, - ) - } + /* activityFactory= */ SingleActivityFactory { + TestableControlsActivity( + uiController, + broadcastDispatcher, + dreamManager, + featureFlags, + controlsSettingsDialogManager, + keyguardStateController, + ) }, - false, - false + /* initialTouchMode= */ false, + /* launchActivity= */ false, ) @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java index 729a1ccd30f9..ce8028c8b37a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java @@ -19,6 +19,7 @@ package com.android.systemui.keyguard; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -31,6 +32,7 @@ import android.content.ContentResolver; import android.media.MediaMetadata; import android.media.session.PlaybackState; import android.net.Uri; +import android.os.Handler; import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -166,6 +168,7 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { @Test public void updatesClock() { + clearInvocations(mContentResolver); mProvider.mKeyguardUpdateMonitorCallback.onTimeChanged(); TestableLooper.get(this).processAllMessages(); verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null)); @@ -217,11 +220,13 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { reset(mContentResolver); mProvider.onPrimaryMetadataOrStateChanged(mock(MediaMetadata.class), PlaybackState.STATE_PLAYING); + TestableLooper.get(this).processAllMessages(); verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null)); // Hides after waking up reset(mContentResolver); mProvider.onDozingChanged(false); + TestableLooper.get(this).processAllMessages(); verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null)); } @@ -231,6 +236,7 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { mProvider.onPrimaryMetadataOrStateChanged(mock(MediaMetadata.class), PlaybackState.STATE_PLAYING); reset(mContentResolver); + TestableLooper.get(this).processAllMessages(); // Show media when dozing mProvider.onDozingChanged(true); verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null)); @@ -272,6 +278,8 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { mMediaManager = KeyguardSliceProviderTest.this.mNotificationMediaManager; mKeyguardUpdateMonitor = KeyguardSliceProviderTest.this.mKeyguardUpdateMonitor; mUserTracker = KeyguardSliceProviderTest.this.mUserTracker; + mBgHandler = + new Handler(TestableLooper.get(KeyguardSliceProviderTest.this).getLooper()); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt index 56698e0ec41c..d1299d40ea12 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt @@ -261,7 +261,6 @@ class MediaDataManagerTest : SysuiTestCase() { whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(SMARTSPACE_CREATION_TIME) whenever(mediaSmartspaceTarget.expiryTimeMillis).thenReturn(SMARTSPACE_EXPIRY_TIME) whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false) - whenever(mediaFlags.isExplicitIndicatorEnabled()).thenReturn(true) whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false) whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false) whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(false) 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 f902be3c1a29..b4b307301138 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 @@ -233,7 +233,6 @@ public class MediaControlPanelTest : SysuiTestCase() { FakeFeatureFlags().apply { this.set(Flags.UMO_SURFACE_RIPPLE, false) this.set(Flags.UMO_TURBULENCE_NOISE, false) - this.set(Flags.MEDIA_EXPLICIT_INDICATOR, true) this.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, false) } @Mock private lateinit var globalSettings: GlobalSettings @@ -1793,7 +1792,7 @@ public class MediaControlPanelTest : SysuiTestCase() { // THEN it sends the PendingIntent without dismissing keyguard first, // and does not use the Intent directly (see b/271845008) captor.value.onClick(viewHolder.player) - verify(pendingIntent).send() + verify(pendingIntent).send(any(Bundle::class.java)) verify(pendingIntent, never()).getIntent() verify(activityStarter, never()).postStartActivityDismissingKeyguard(eq(clickIntent), any()) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt new file mode 100644 index 000000000000..bcbf666fd302 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt @@ -0,0 +1,67 @@ +/* + * 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.taskswitcher + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator +import com.android.systemui.util.mockito.whenever +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyZeroInteractions +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class MediaProjectionTaskSwitcherCoreStartableTest : SysuiTestCase() { + + @Mock private lateinit var flags: FeatureFlags + @Mock private lateinit var coordinator: TaskSwitcherNotificationCoordinator + + private lateinit var coreStartable: MediaProjectionTaskSwitcherCoreStartable + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + coreStartable = MediaProjectionTaskSwitcherCoreStartable(coordinator, flags) + } + + @Test + fun start_flagEnabled_startsCoordinator() { + whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(true) + + coreStartable.start() + + verify(coordinator).start() + } + + @Test + fun start_flagDisabled_doesNotStartCoordinator() { + whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(false) + + coreStartable.start() + + verifyZeroInteractions(coordinator) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt new file mode 100644 index 000000000000..83932b0a6133 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt @@ -0,0 +1,108 @@ +/* + * 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.taskswitcher.data.repository + +import android.os.Binder +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask +import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidTestingRunner::class) +@SmallTest +class ActivityTaskManagerTasksRepositoryTest : SysuiTestCase() { + + private val fakeActivityTaskManager = FakeActivityTaskManager() + + private val dispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(dispatcher) + + private val repo = + ActivityTaskManagerTasksRepository( + activityTaskManager = fakeActivityTaskManager.activityTaskManager, + applicationScope = testScope.backgroundScope, + backgroundDispatcher = dispatcher + ) + + @Test + fun findRunningTaskFromWindowContainerToken_noMatch_returnsNull() { + fakeActivityTaskManager.addRunningTasks(createTask(taskId = 1), createTask(taskId = 2)) + + testScope.runTest { + val matchingTask = + repo.findRunningTaskFromWindowContainerToken(windowContainerToken = Binder()) + + assertThat(matchingTask).isNull() + } + } + + @Test + fun findRunningTaskFromWindowContainerToken_matchingToken_returnsTaskInfo() { + val expectedToken = createToken() + val expectedTask = createTask(taskId = 1, token = expectedToken) + + fakeActivityTaskManager.addRunningTasks( + createTask(taskId = 2), + expectedTask, + ) + + testScope.runTest { + val actualTask = + repo.findRunningTaskFromWindowContainerToken( + windowContainerToken = expectedToken.asBinder() + ) + + assertThat(actualTask).isEqualTo(expectedTask) + } + } + + @Test + fun foregroundTask_returnsStreamOfTasksMovedToFront() = + testScope.runTest { + val foregroundTask by collectLastValue(repo.foregroundTask) + + fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1)) + assertThat(foregroundTask?.taskId).isEqualTo(1) + + fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 2)) + assertThat(foregroundTask?.taskId).isEqualTo(2) + + fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 3)) + assertThat(foregroundTask?.taskId).isEqualTo(3) + } + + @Test + fun foregroundTask_lastValueIsCached() = + testScope.runTest { + val foregroundTaskA by collectLastValue(repo.foregroundTask) + fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1)) + assertThat(foregroundTaskA?.taskId).isEqualTo(1) + + val foregroundTaskB by collectLastValue(repo.foregroundTask) + assertThat(foregroundTaskB?.taskId).isEqualTo(1) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt new file mode 100644 index 000000000000..1c4870bc32b1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt @@ -0,0 +1,77 @@ +/* + * 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.taskswitcher.data.repository + +import android.app.ActivityManager.RunningTaskInfo +import android.app.ActivityTaskManager +import android.app.TaskStackListener +import android.content.Intent +import android.window.IWindowContainerToken +import android.window.WindowContainerToken +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever + +class FakeActivityTaskManager { + + private val runningTasks = mutableListOf<RunningTaskInfo>() + private val taskTaskListeners = mutableListOf<TaskStackListener>() + + val activityTaskManager = mock<ActivityTaskManager>() + + init { + whenever(activityTaskManager.registerTaskStackListener(any())).thenAnswer { + taskTaskListeners += it.arguments[0] as TaskStackListener + return@thenAnswer Unit + } + whenever(activityTaskManager.unregisterTaskStackListener(any())).thenAnswer { + taskTaskListeners -= it.arguments[0] as TaskStackListener + return@thenAnswer Unit + } + whenever(activityTaskManager.getTasks(any())).thenAnswer { + val maxNumTasks = it.arguments[0] as Int + return@thenAnswer runningTasks.take(maxNumTasks) + } + } + + fun moveTaskToForeground(task: RunningTaskInfo) { + taskTaskListeners.forEach { it.onTaskMovedToFront(task) } + } + + fun addRunningTasks(vararg tasks: RunningTaskInfo) { + runningTasks += tasks + } + + companion object { + + fun createTask( + taskId: Int, + token: WindowContainerToken = createToken(), + baseIntent: Intent = Intent() + ) = + RunningTaskInfo().apply { + this.taskId = taskId + this.token = token + this.baseIntent = baseIntent + } + + fun createToken(): WindowContainerToken { + val realToken = object : IWindowContainerToken.Stub() {} + return WindowContainerToken(realToken) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt new file mode 100644 index 000000000000..c59fd60cca9b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt @@ -0,0 +1,42 @@ +/* + * 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.taskswitcher.data.repository + +import android.app.TaskInfo +import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeMediaProjectionRepository : MediaProjectionRepository { + + private val state = MutableStateFlow<MediaProjectionState>(MediaProjectionState.NotProjecting) + + fun switchProjectedTask(newTask: TaskInfo) { + state.value = MediaProjectionState.SingleTask(newTask) + } + + override val mediaProjectionState: Flow<MediaProjectionState> = state.asStateFlow() + + fun projectEntireScreen() { + state.value = MediaProjectionState.EntireScreen + } + + fun stopProjecting() { + state.value = MediaProjectionState.NotProjecting + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt new file mode 100644 index 000000000000..593e3893fb2a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt @@ -0,0 +1,68 @@ +/* + * 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.taskswitcher.data.repository + +import android.app.ActivityManager.RunningTaskInfo +import android.content.Intent +import android.os.IBinder +import android.window.IWindowContainerToken +import android.window.WindowContainerToken +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeTasksRepository : TasksRepository { + + private val _foregroundTask = MutableStateFlow(DEFAULT_TASK) + + override val foregroundTask: Flow<RunningTaskInfo> = _foregroundTask.asStateFlow() + + private val runningTasks = mutableListOf(DEFAULT_TASK) + + override suspend fun findRunningTaskFromWindowContainerToken( + windowContainerToken: IBinder + ): RunningTaskInfo? = runningTasks.firstOrNull { it.token.asBinder() == windowContainerToken } + + fun addRunningTask(task: RunningTaskInfo) { + runningTasks.add(task) + } + + fun moveTaskToForeground(task: RunningTaskInfo) { + _foregroundTask.value = task + } + + companion object { + val DEFAULT_TASK = createTask(taskId = -1) + val LAUNCHER_INTENT: Intent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME) + + fun createTask( + taskId: Int, + token: WindowContainerToken = createToken(), + baseIntent: Intent = Intent() + ) = + RunningTaskInfo().apply { + this.taskId = taskId + this.token = token + this.baseIntent = baseIntent + } + + fun createToken(): WindowContainerToken { + val realToken = object : IWindowContainerToken.Stub() {} + return WindowContainerToken(realToken) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt new file mode 100644 index 000000000000..2b074655bb02 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt @@ -0,0 +1,162 @@ +/* + * 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.taskswitcher.data.repository + +import android.media.projection.MediaProjectionInfo +import android.media.projection.MediaProjectionManager +import android.os.Binder +import android.os.Handler +import android.os.UserHandle +import android.testing.AndroidTestingRunner +import android.view.ContentRecordingSession +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidTestingRunner::class) +@SmallTest +class MediaProjectionManagerRepositoryTest : SysuiTestCase() { + + private val mediaProjectionManager = mock<MediaProjectionManager>() + + private val dispatcher = StandardTestDispatcher() + private val testScope = TestScope(dispatcher) + private val tasksRepo = FakeTasksRepository() + + private lateinit var callback: MediaProjectionManager.Callback + private lateinit var repo: MediaProjectionManagerRepository + + @Before + fun setUp() { + whenever(mediaProjectionManager.addCallback(any(), any())).thenAnswer { + callback = it.arguments[0] as MediaProjectionManager.Callback + return@thenAnswer Unit + } + repo = + MediaProjectionManagerRepository( + mediaProjectionManager = mediaProjectionManager, + handler = Handler.getMain(), + applicationScope = testScope.backgroundScope, + tasksRepository = tasksRepo + ) + } + + @Test + fun mediaProjectionState_onStart_emitsNotProjecting() = + testScope.runTest { + val state by collectLastValue(repo.mediaProjectionState) + runCurrent() + + callback.onStart(TEST_MEDIA_INFO) + + assertThat(state).isEqualTo(MediaProjectionState.NotProjecting) + } + + @Test + fun mediaProjectionState_onStop_emitsNotProjecting() = + testScope.runTest { + val state by collectLastValue(repo.mediaProjectionState) + runCurrent() + + callback.onStop(TEST_MEDIA_INFO) + + assertThat(state).isEqualTo(MediaProjectionState.NotProjecting) + } + + @Test + fun mediaProjectionState_onSessionSet_sessionNull_emitsNotProjecting() = + testScope.runTest { + val state by collectLastValue(repo.mediaProjectionState) + runCurrent() + + callback.onRecordingSessionSet(TEST_MEDIA_INFO, /* session= */ null) + + assertThat(state).isEqualTo(MediaProjectionState.NotProjecting) + } + + @Test + fun mediaProjectionState_onSessionSet_contentToRecordDisplay_emitsEntireScreen() = + testScope.runTest { + val state by collectLastValue(repo.mediaProjectionState) + runCurrent() + + val session = ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123) + callback.onRecordingSessionSet(TEST_MEDIA_INFO, session) + + assertThat(state).isEqualTo(MediaProjectionState.EntireScreen) + } + + @Test + fun mediaProjectionState_onSessionSet_tokenNull_emitsEntireScreen() = + testScope.runTest { + val state by collectLastValue(repo.mediaProjectionState) + runCurrent() + + val session = + ContentRecordingSession.createTaskSession(/* taskWindowContainerToken= */ null) + callback.onRecordingSessionSet(TEST_MEDIA_INFO, session) + + assertThat(state).isEqualTo(MediaProjectionState.EntireScreen) + } + + @Test + fun mediaProjectionState_sessionSet_taskWithToken_noMatchingRunningTask_emitsEntireScreen() = + testScope.runTest { + val state by collectLastValue(repo.mediaProjectionState) + runCurrent() + + val taskWindowContainerToken = Binder() + val session = ContentRecordingSession.createTaskSession(taskWindowContainerToken) + callback.onRecordingSessionSet(TEST_MEDIA_INFO, session) + + assertThat(state).isEqualTo(MediaProjectionState.EntireScreen) + } + + @Test + fun mediaProjectionState_sessionSet_taskWithToken_matchingRunningTask_emitsSingleTask() = + testScope.runTest { + val token = FakeTasksRepository.createToken() + val task = FakeTasksRepository.createTask(taskId = 1, token = token) + tasksRepo.addRunningTask(task) + val state by collectLastValue(repo.mediaProjectionState) + runCurrent() + + val session = ContentRecordingSession.createTaskSession(token.asBinder()) + callback.onRecordingSessionSet(TEST_MEDIA_INFO, session) + + assertThat(state).isEqualTo(MediaProjectionState.SingleTask(task)) + } + + companion object { + val TEST_MEDIA_INFO = + MediaProjectionInfo(/* packageName= */ "com.test.package", UserHandle.CURRENT) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt new file mode 100644 index 000000000000..112950b860e8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt @@ -0,0 +1,127 @@ +/* + * 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.taskswitcher.domain.interactor + +import android.content.Intent +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository +import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager +import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask +import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionRepository +import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidTestingRunner::class) +@SmallTest +class TaskSwitchInteractorTest : SysuiTestCase() { + + private val dispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(dispatcher) + + private val fakeActivityTaskManager = FakeActivityTaskManager() + private val mediaRepo = FakeMediaProjectionRepository() + private val tasksRepo = + ActivityTaskManagerTasksRepository( + activityTaskManager = fakeActivityTaskManager.activityTaskManager, + applicationScope = testScope.backgroundScope, + backgroundDispatcher = dispatcher + ) + + private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo) + + @Test + fun taskSwitchChanges_notProjecting_foregroundTaskChange_emitsNotProjectingTask() = + testScope.runTest { + mediaRepo.stopProjecting() + val taskSwitchState by collectLastValue(interactor.taskSwitchChanges) + + fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1)) + + assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask) + } + + @Test + fun taskSwitchChanges_projectingScreen_foregroundTaskChange_emitsNotProjectingTask() = + testScope.runTest { + mediaRepo.projectEntireScreen() + val taskSwitchState by collectLastValue(interactor.taskSwitchChanges) + + fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1)) + + assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask) + } + + @Test + fun taskSwitchChanges_projectingTask_foregroundTaskDifferent_emitsTaskChanged() = + testScope.runTest { + val projectedTask = createTask(taskId = 0) + val foregroundTask = createTask(taskId = 1) + mediaRepo.switchProjectedTask(projectedTask) + val taskSwitchState by collectLastValue(interactor.taskSwitchChanges) + + fakeActivityTaskManager.moveTaskToForeground(foregroundTask) + + assertThat(taskSwitchState) + .isEqualTo( + TaskSwitchState.TaskSwitched( + projectedTask = projectedTask, + foregroundTask = foregroundTask + ) + ) + } + + @Test + fun taskSwitchChanges_projectingTask_foregroundTaskLauncher_emitsTaskUnchanged() = + testScope.runTest { + val projectedTask = createTask(taskId = 0) + val foregroundTask = createTask(taskId = 1, baseIntent = LAUNCHER_INTENT) + mediaRepo.switchProjectedTask(projectedTask) + val taskSwitchState by collectLastValue(interactor.taskSwitchChanges) + + fakeActivityTaskManager.moveTaskToForeground(foregroundTask) + + assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged) + } + + @Test + fun taskSwitchChanges_projectingTask_foregroundTaskSame_emitsTaskUnchanged() = + testScope.runTest { + val projectedTask = createTask(taskId = 0) + val foregroundTask = createTask(taskId = 0) + mediaRepo.switchProjectedTask(projectedTask) + val taskSwitchState by collectLastValue(interactor.taskSwitchChanges) + + fakeActivityTaskManager.moveTaskToForeground(foregroundTask) + + assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged) + } + + companion object { + private val LAUNCHER_INTENT: Intent = + Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt new file mode 100644 index 000000000000..ea44fb3b1f6e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt @@ -0,0 +1,141 @@ +/* + * 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.taskswitcher.ui.viewmodel + +import android.content.Intent +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository +import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager +import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask +import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionRepository +import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor +import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidTestingRunner::class) +@SmallTest +class TaskSwitcherNotificationViewModelTest : SysuiTestCase() { + + private val dispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(dispatcher) + + private val fakeActivityTaskManager = FakeActivityTaskManager() + private val mediaRepo = FakeMediaProjectionRepository() + private val tasksRepo = + ActivityTaskManagerTasksRepository( + activityTaskManager = fakeActivityTaskManager.activityTaskManager, + applicationScope = testScope.backgroundScope, + backgroundDispatcher = dispatcher + ) + private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo) + + private val viewModel = TaskSwitcherNotificationViewModel(interactor) + + @Test + fun uiState_notProjecting_emitsNotShowing() = + testScope.runTest { + mediaRepo.stopProjecting() + val uiState by collectLastValue(viewModel.uiState) + + assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing) + } + + @Test + fun uiState_notProjecting_foregroundTaskChanged_emitsNotShowing() = + testScope.runTest { + mediaRepo.stopProjecting() + val uiState by collectLastValue(viewModel.uiState) + + mediaRepo.switchProjectedTask(createTask(taskId = 1)) + + assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing) + } + + @Test + fun uiState_projectingEntireScreen_emitsNotShowing() = + testScope.runTest { + mediaRepo.projectEntireScreen() + val uiState by collectLastValue(viewModel.uiState) + + assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing) + } + + @Test + fun uiState_projectingEntireScreen_foregroundTaskChanged_emitsNotShowing() = + testScope.runTest { + mediaRepo.projectEntireScreen() + val uiState by collectLastValue(viewModel.uiState) + + mediaRepo.switchProjectedTask(createTask(taskId = 1)) + + assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing) + } + + @Test + fun uiState_projectingTask_foregroundTaskChanged_different_emitsShowing() = + testScope.runTest { + val projectedTask = createTask(taskId = 1) + val foregroundTask = createTask(taskId = 2) + mediaRepo.switchProjectedTask(projectedTask) + val uiState by collectLastValue(viewModel.uiState) + + fakeActivityTaskManager.moveTaskToForeground(foregroundTask) + + assertThat(uiState) + .isEqualTo(TaskSwitcherNotificationUiState.Showing(projectedTask, foregroundTask)) + } + + @Test + fun uiState_projectingTask_foregroundTaskChanged_same_emitsNotShowing() = + testScope.runTest { + val projectedTask = createTask(taskId = 1) + mediaRepo.switchProjectedTask(projectedTask) + val uiState by collectLastValue(viewModel.uiState) + + fakeActivityTaskManager.moveTaskToForeground(projectedTask) + + assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing) + } + + @Test + fun uiState_projectingTask_foregroundTaskChanged_different_taskIsLauncher_emitsNotShowing() = + testScope.runTest { + val projectedTask = createTask(taskId = 1) + val foregroundTask = createTask(taskId = 2, baseIntent = LAUNCHER_INTENT) + mediaRepo.switchProjectedTask(projectedTask) + val uiState by collectLastValue(viewModel.uiState) + + fakeActivityTaskManager.moveTaskToForeground(foregroundTask) + + assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing) + } + + companion object { + private val LAUNCHER_INTENT: Intent = + Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt index 36b913fc3e7b..bdb095a3a209 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt @@ -22,9 +22,9 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule -import androidx.test.runner.intercepting.SingleActivityFactory import com.android.dx.mockito.inline.extended.ExtendedMockito.verify import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.Companion.ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE import com.android.systemui.util.mockito.any @@ -47,13 +47,9 @@ class LaunchNotesRoleSettingsTrampolineActivityTest : SysuiTestCase() { @Rule @JvmField val activityRule = - ActivityTestRule<LaunchNotesRoleSettingsTrampolineActivity>( - /* activityFactory= */ object : - SingleActivityFactory<LaunchNotesRoleSettingsTrampolineActivity>( - LaunchNotesRoleSettingsTrampolineActivity::class.java - ) { - override fun create(intent: Intent?) = - LaunchNotesRoleSettingsTrampolineActivity(noteTaskController) + ActivityTestRule( + /* activityFactory= */ SingleActivityFactory { + LaunchNotesRoleSettingsTrampolineActivity(noteTaskController) }, /* initialTouchMode= */ false, /* launchActivity= */ false, diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt index 627c4a80e1ec..1f0f0d70a858 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt @@ -16,14 +16,13 @@ package com.android.systemui.notetask.shortcut -import android.content.Intent import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule -import androidx.test.runner.intercepting.SingleActivityFactory import com.android.dx.mockito.inline.extended.ExtendedMockito.verify import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.notetask.NoteTaskController import com.android.systemui.notetask.NoteTaskEntryPoint import com.android.systemui.util.mockito.any @@ -47,12 +46,8 @@ class LaunchNoteTaskActivityTest : SysuiTestCase() { @JvmField val activityRule = ActivityTestRule<LaunchNoteTaskActivity>( - /* activityFactory= */ object : - SingleActivityFactory<LaunchNoteTaskActivity>(LaunchNoteTaskActivity::class.java) { - override fun create(intent: Intent?) = - LaunchNoteTaskActivity( - controller = noteTaskController, - ) + /* activityFactory= */ SingleActivityFactory { + LaunchNoteTaskActivity(controller = noteTaskController) }, /* initialTouchMode= */ false, /* launchActivity= */ false, diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt index 764619340592..16751c937f9e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.settings.brightness -import android.content.Intent import android.graphics.Rect import android.os.Handler import android.testing.AndroidTestingRunner @@ -25,9 +24,9 @@ import android.view.View import android.view.ViewGroup import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule -import androidx.test.runner.intercepting.SingleActivityFactory import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.settings.FakeDisplayTracker import com.android.systemui.settings.UserTracker import com.android.systemui.util.mockito.any @@ -59,19 +58,17 @@ class BrightnessDialogTest : SysuiTestCase() { @JvmField var activityRule = ActivityTestRule( - object : SingleActivityFactory<TestDialog>(TestDialog::class.java) { - override fun create(intent: Intent?): TestDialog { - return TestDialog( - userTracker, - displayTracker, - brightnessSliderControllerFactory, - mainExecutor, - backgroundHandler - ) - } + /* activityFactory= */ SingleActivityFactory { + TestDialog( + userTracker, + displayTracker, + brightnessSliderControllerFactory, + mainExecutor, + backgroundHandler + ) }, - false, - false + /* initialTouchMode= */ false, + /* launchActivity= */ false, ) @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt index 2fbe87158eba..ea70e9e44c66 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt @@ -32,7 +32,6 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder @@ -46,11 +45,14 @@ import com.android.systemui.statusbar.notification.interruption.KeyguardNotifica import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.withArgCaptor import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat +import java.util.function.Consumer +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestCoroutineScheduler @@ -62,9 +64,8 @@ import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.same import org.mockito.Mockito.anyString import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.never import org.mockito.Mockito.verify -import java.util.function.Consumer -import kotlin.time.Duration.Companion.seconds import org.mockito.Mockito.`when` as whenever @SmallTest @@ -75,7 +76,6 @@ class KeyguardCoordinatorTest : SysuiTestCase() { private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock() private val keyguardRepository = FakeKeyguardRepository() private val keyguardTransitionRepository = FakeKeyguardTransitionRepository() - private val notifPipelineFlags: NotifPipelineFlags = mock() private val notifPipeline: NotifPipeline = mock() private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock() private val statusBarStateController: StatusBarStateController = mock() @@ -136,13 +136,8 @@ class KeyguardCoordinatorTest : SysuiTestCase() { ) testScheduler.runCurrent() - // WHEN: The shade is expanded - whenever(statusBarStateController.isExpanded).thenReturn(true) - statusBarStateListener.onExpandedChanged(true) - testScheduler.runCurrent() - - // THEN: The notification is still treated as "unseen" and is not filtered out. - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() + // THEN: We are no longer listening for shade expansions + verify(statusBarStateController, never()).addCallback(any()) } } @@ -152,6 +147,10 @@ class KeyguardCoordinatorTest : SysuiTestCase() { keyguardRepository.setKeyguardShowing(false) whenever(statusBarStateController.isExpanded).thenReturn(false) runKeyguardCoordinatorTest { + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE) + ) + // WHEN: A notification is posted val fakeEntry = NotificationEntryBuilder().build() collectionListener.onEntryAdded(fakeEntry) @@ -162,6 +161,9 @@ class KeyguardCoordinatorTest : SysuiTestCase() { // WHEN: The keyguard is now showing keyguardRepository.setKeyguardShowing(true) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.AOD) + ) testScheduler.runCurrent() // THEN: The notification is recognized as "seen" and is filtered out. @@ -169,6 +171,9 @@ class KeyguardCoordinatorTest : SysuiTestCase() { // WHEN: The keyguard goes away keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.AOD, to = KeyguardState.GONE) + ) testScheduler.runCurrent() // THEN: The notification is shown regardless @@ -182,9 +187,10 @@ class KeyguardCoordinatorTest : SysuiTestCase() { keyguardRepository.setKeyguardShowing(false) whenever(statusBarStateController.isExpanded).thenReturn(true) runKeyguardCoordinatorTest { - val fakeEntry = NotificationEntryBuilder() + val fakeEntry = + NotificationEntryBuilder() .setNotification(Notification.Builder(mContext, "id").setOngoing(true).build()) - .build() + .build() collectionListener.onEntryAdded(fakeEntry) // WHEN: The keyguard is now showing @@ -202,11 +208,13 @@ class KeyguardCoordinatorTest : SysuiTestCase() { keyguardRepository.setKeyguardShowing(false) whenever(statusBarStateController.isExpanded).thenReturn(true) runKeyguardCoordinatorTest { - val fakeEntry = NotificationEntryBuilder().build().apply { - row = mock<ExpandableNotificationRow>().apply { - whenever(isMediaRow).thenReturn(true) + val fakeEntry = + NotificationEntryBuilder().build().apply { + row = + mock<ExpandableNotificationRow>().apply { + whenever(isMediaRow).thenReturn(true) + } } - } collectionListener.onEntryAdded(fakeEntry) // WHEN: The keyguard is now showing @@ -299,14 +307,12 @@ class KeyguardCoordinatorTest : SysuiTestCase() { runKeyguardCoordinatorTest { // WHEN: A new notification is posted val fakeSummary = NotificationEntryBuilder().build() - val fakeChild = NotificationEntryBuilder() + val fakeChild = + NotificationEntryBuilder() .setGroup(context, "group") .setGroupSummary(context, false) .build() - GroupEntryBuilder() - .setSummary(fakeSummary) - .addChild(fakeChild) - .build() + GroupEntryBuilder().setSummary(fakeSummary).addChild(fakeChild).build() collectionListener.onEntryAdded(fakeSummary) collectionListener.onEntryAdded(fakeChild) @@ -331,6 +337,10 @@ class KeyguardCoordinatorTest : SysuiTestCase() { runKeyguardCoordinatorTest { val fakeEntry = NotificationEntryBuilder().build() collectionListener.onEntryAdded(fakeEntry) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.AOD, to = KeyguardState.LOCKSCREEN) + ) + testScheduler.runCurrent() // WHEN: five seconds have passed testScheduler.advanceTimeBy(5.seconds) @@ -338,10 +348,16 @@ class KeyguardCoordinatorTest : SysuiTestCase() { // WHEN: Keyguard is no longer showing keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE) + ) testScheduler.runCurrent() // WHEN: Keyguard is shown again keyguardRepository.setKeyguardShowing(true) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.AOD) + ) testScheduler.runCurrent() // THEN: The notification is now recognized as "seen" and is filtered out. @@ -354,11 +370,17 @@ class KeyguardCoordinatorTest : SysuiTestCase() { // GIVEN: Keyguard is showing, unseen notification is present keyguardRepository.setKeyguardShowing(true) runKeyguardCoordinatorTest { + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN) + ) val fakeEntry = NotificationEntryBuilder().build() collectionListener.onEntryAdded(fakeEntry) // WHEN: Keyguard is no longer showing keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE) + ) // WHEN: Keyguard is shown again keyguardRepository.setKeyguardShowing(true) @@ -369,14 +391,212 @@ class KeyguardCoordinatorTest : SysuiTestCase() { } } + @Test + fun unseenNotificationIsNotMarkedAsSeenIfNotOnKeyguardLongEnough() { + // GIVEN: Keyguard is showing, not dozing, unseen notification is present + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setIsDozing(false) + runKeyguardCoordinatorTest { + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN) + ) + val firstEntry = NotificationEntryBuilder().setId(1).build() + collectionListener.onEntryAdded(firstEntry) + testScheduler.runCurrent() + + // WHEN: one second has passed + testScheduler.advanceTimeBy(1.seconds) + testScheduler.runCurrent() + + // WHEN: another unseen notification is posted + val secondEntry = NotificationEntryBuilder().setId(2).build() + collectionListener.onEntryAdded(secondEntry) + testScheduler.runCurrent() + + // WHEN: four more seconds have passed + testScheduler.advanceTimeBy(4.seconds) + testScheduler.runCurrent() + + // WHEN: the keyguard is no longer showing + keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE) + ) + testScheduler.runCurrent() + + // WHEN: Keyguard is shown again + keyguardRepository.setKeyguardShowing(true) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN) + ) + testScheduler.runCurrent() + + // THEN: The first notification is considered seen and is filtered out. + assertThat(unseenFilter.shouldFilterOut(firstEntry, 0L)).isTrue() + + // THEN: The second notification is still considered unseen and is not filtered out + assertThat(unseenFilter.shouldFilterOut(secondEntry, 0L)).isFalse() + } + } + + @Test + fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedAfterThreshold() { + // GIVEN: Keyguard is showing, not dozing + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setIsDozing(false) + runKeyguardCoordinatorTest { + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN) + ) + testScheduler.runCurrent() + + // WHEN: a new notification is posted + val entry = NotificationEntryBuilder().setId(1).build() + collectionListener.onEntryAdded(entry) + testScheduler.runCurrent() + + // WHEN: five more seconds have passed + testScheduler.advanceTimeBy(5.seconds) + testScheduler.runCurrent() + + // WHEN: the notification is removed + collectionListener.onEntryRemoved(entry, 0) + testScheduler.runCurrent() + + // WHEN: the notification is re-posted + collectionListener.onEntryAdded(entry) + testScheduler.runCurrent() + + // WHEN: one more second has passed + testScheduler.advanceTimeBy(1.seconds) + testScheduler.runCurrent() + + // WHEN: the keyguard is no longer showing + keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE) + ) + testScheduler.runCurrent() + + // WHEN: Keyguard is shown again + keyguardRepository.setKeyguardShowing(true) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN) + ) + testScheduler.runCurrent() + + // THEN: The notification is considered unseen and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse() + } + } + + @Test + fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedBeforeThreshold() { + // GIVEN: Keyguard is showing, not dozing + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setIsDozing(false) + runKeyguardCoordinatorTest { + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN) + ) + testScheduler.runCurrent() + + // WHEN: a new notification is posted + val entry = NotificationEntryBuilder().setId(1).build() + collectionListener.onEntryAdded(entry) + testScheduler.runCurrent() + + // WHEN: one second has passed + testScheduler.advanceTimeBy(1.seconds) + testScheduler.runCurrent() + + // WHEN: the notification is removed + collectionListener.onEntryRemoved(entry, 0) + testScheduler.runCurrent() + + // WHEN: the notification is re-posted + collectionListener.onEntryAdded(entry) + testScheduler.runCurrent() + + // WHEN: one more second has passed + testScheduler.advanceTimeBy(1.seconds) + testScheduler.runCurrent() + + // WHEN: the keyguard is no longer showing + keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE) + ) + testScheduler.runCurrent() + + // WHEN: Keyguard is shown again + keyguardRepository.setKeyguardShowing(true) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN) + ) + testScheduler.runCurrent() + + // THEN: The notification is considered unseen and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse() + } + } + + @Test + fun unseenNotificationOnKeyguardNotMarkedAsSeenIfUpdatedBeforeThreshold() { + // GIVEN: Keyguard is showing, not dozing + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setIsDozing(false) + runKeyguardCoordinatorTest { + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN) + ) + testScheduler.runCurrent() + + // WHEN: a new notification is posted + val entry = NotificationEntryBuilder().setId(1).build() + collectionListener.onEntryAdded(entry) + testScheduler.runCurrent() + + // WHEN: one second has passed + testScheduler.advanceTimeBy(1.seconds) + testScheduler.runCurrent() + + // WHEN: the notification is updated + collectionListener.onEntryUpdated(entry) + testScheduler.runCurrent() + + // WHEN: four more seconds have passed + testScheduler.advanceTimeBy(4.seconds) + testScheduler.runCurrent() + + // WHEN: the keyguard is no longer showing + keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE) + ) + testScheduler.runCurrent() + + // WHEN: Keyguard is shown again + keyguardRepository.setKeyguardShowing(true) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN) + ) + testScheduler.runCurrent() + + // THEN: The notification is considered unseen and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse() + } + } + private fun runKeyguardCoordinatorTest( testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit ) { val testDispatcher = UnconfinedTestDispatcher() val testScope = TestScope(testDispatcher) - val fakeSettings = FakeSettings().apply { - putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1) - } + val fakeSettings = + FakeSettings().apply { + putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1) + } val seenNotificationsProvider = SeenNotificationsProviderImpl() val keyguardCoordinator = KeyguardCoordinator( @@ -387,7 +607,6 @@ class KeyguardCoordinatorTest : SysuiTestCase() { keyguardRepository, keyguardTransitionRepository, mock<KeyguardCoordinatorLogger>(), - notifPipelineFlags, testScope.backgroundScope, sectionHeaderVisibilityProvider, fakeSettings, @@ -397,11 +616,12 @@ class KeyguardCoordinatorTest : SysuiTestCase() { keyguardCoordinator.attach(notifPipeline) testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) { KeyguardCoordinatorTestScope( - keyguardCoordinator, - testScope, - seenNotificationsProvider, - fakeSettings, - ).testBlock() + keyguardCoordinator, + testScope, + seenNotificationsProvider, + fakeSettings, + ) + .testBlock() } } @@ -414,10 +634,9 @@ class KeyguardCoordinatorTest : SysuiTestCase() { val testScheduler: TestCoroutineScheduler get() = scope.testScheduler - val onStateChangeListener: Consumer<String> = - withArgCaptor { - verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture()) - } + val onStateChangeListener: Consumer<String> = withArgCaptor { + verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture()) + } val unseenFilter: NotifFilter get() = keyguardCoordinator.unseenNotifFilter @@ -426,11 +645,11 @@ class KeyguardCoordinatorTest : SysuiTestCase() { verify(notifPipeline).addCollectionListener(capture()) } - val onHeadsUpChangedListener: OnHeadsUpChangedListener get() = - withArgCaptor { verify(headsUpManager).addListener(capture()) } + val onHeadsUpChangedListener: OnHeadsUpChangedListener + get() = withArgCaptor { verify(headsUpManager).addListener(capture()) } - val statusBarStateListener: StatusBarStateController.StateListener get() = - withArgCaptor { verify(statusBarStateController).addCallback(capture()) } + val statusBarStateListener: StatusBarStateController.StateListener + get() = withArgCaptor { verify(statusBarStateController).addCallback(capture()) } var showOnlyUnseenNotifsOnKeyguardSetting: Boolean get() = diff --git a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt index b30c20db642d..b04eb01201fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt @@ -25,8 +25,8 @@ import android.testing.TestableLooper import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule -import androidx.test.runner.intercepting.SingleActivityFactory import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.SingleActivityFactory import com.google.common.truth.Truth.assertThat import javax.inject.Inject @@ -49,19 +49,17 @@ class UsbPermissionActivityTest : SysuiTestCase() { open class UsbPermissionActivityTestable @Inject constructor ( val message: UsbAudioWarningDialogMessage - ) - : UsbPermissionActivity(UsbAudioWarningDialogMessage()) + ) : UsbPermissionActivity(UsbAudioWarningDialogMessage()) @Rule @JvmField - var activityRule = ActivityTestRule<UsbPermissionActivityTestable>( - object : SingleActivityFactory<UsbPermissionActivityTestable>( - UsbPermissionActivityTestable::class.java - ) { - override fun create(intent: Intent?): UsbPermissionActivityTestable { - return UsbPermissionActivityTestable(mMessage) - } - }, false, false) + var activityRule = ActivityTestRule( + /* activityFactory= */ SingleActivityFactory { + UsbPermissionActivityTestable(mMessage) + }, + /* initialTouchMode= */ false, + /* launchActivity= */ false, + ) private val activityIntent = Intent(mContext, UsbPermissionActivityTestable::class.java) .apply { diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index a09af002ab26..ef12b2af3a66 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -39,6 +39,7 @@ import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -153,6 +154,7 @@ import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.taskview.TaskViewTransitions; +import com.android.wm.shell.transition.Transitions; import org.junit.After; import org.junit.Before; @@ -282,7 +284,7 @@ public class BubblesTest extends SysuiTestCase { @Mock private ScreenOffAnimationController mScreenOffAnimationController; @Mock - private TaskViewTransitions mTaskViewTransitions; + Transitions mTransitions; @Mock private Optional<OneHandedController> mOneHandedOptional; @Mock @@ -294,6 +296,8 @@ public class BubblesTest extends SysuiTestCase { @Mock private Icon mAppBubbleIcon; + private TaskViewTransitions mTaskViewTransitions; + private TestableBubblePositioner mPositioner; private BubbleData mBubbleData; @@ -309,6 +313,12 @@ public class BubblesTest extends SysuiTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + doReturn(true).when(mTransitions).isRegistered(); + } + mTaskViewTransitions = new TaskViewTransitions(mTransitions); + mTestableLooper = TestableLooper.get(this); // For the purposes of this test, just run everything synchronously diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/activity/SingleActivityFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/SingleActivityFactory.kt new file mode 100644 index 000000000000..5a92fb1dbe7f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/SingleActivityFactory.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.activity + +import android.app.Activity +import android.content.Intent +import androidx.test.runner.intercepting.SingleActivityFactory + +/** + * Builds a new [SingleActivityFactory] which delegating any call of [SingleActivityFactory.create] + * to the [instantiate] parameter. + * + * For more details, see [SingleActivityFactory]. + */ +inline fun <reified T : Activity> SingleActivityFactory( + crossinline instantiate: (intent: Intent?) -> T, +): SingleActivityFactory<T> { + return object : SingleActivityFactory<T>(T::class.java) { + override fun create(intent: Intent?): T = instantiate(intent) + } +} diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index e85eee817d29..e3262cfbd30b 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -22,7 +22,6 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; -import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.RouteInfo.RTN_THROW; import static android.net.RouteInfo.RTN_UNREACHABLE; import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN; @@ -280,15 +279,22 @@ public class Vpn { private static final int VPN_DEFAULT_SCORE = 101; /** - * The reset session timer for data stall. If a session has not successfully revalidated after - * the delay, the session will be torn down and restarted in an attempt to recover. Delay + * The recovery timer for data stall. If a session has not successfully revalidated after + * the delay, the session will perform MOBIKE or be restarted in an attempt to recover. Delay * counter is reset on successful validation only. * + * <p>The first {@code MOBIKE_RECOVERY_ATTEMPT} timers are used for performing MOBIKE. + * System will perform session reset for the remaining timers. * <p>If retries have exceeded the length of this array, the last entry in the array will be * used as a repeating interval. */ - private static final long[] DATA_STALL_RESET_DELAYS_SEC = {30L, 60L, 120L, 240L, 480L, 960L}; - + // TODO: use ms instead to speed up the test. + private static final long[] DATA_STALL_RECOVERY_DELAYS_SEC = + {1L, 5L, 30L, 60L, 120L, 240L, 480L, 960L}; + /** + * Maximum attempts to perform MOBIKE when the network is bad. + */ + private static final int MAX_MOBIKE_RECOVERY_ATTEMPT = 2; /** * The initial token value of IKE session. */ @@ -380,6 +386,7 @@ public class Vpn { private final INetworkManagementService mNms; private final INetd mNetd; @VisibleForTesting + @GuardedBy("this") protected VpnConfig mConfig; private final NetworkProvider mNetworkProvider; @VisibleForTesting @@ -392,7 +399,6 @@ public class Vpn { private final UserManager mUserManager; private final VpnProfileStore mVpnProfileStore; - protected boolean mDataStallSuspected = false; @VisibleForTesting VpnProfileStore getVpnProfileStore() { @@ -685,14 +691,14 @@ public class Vpn { } /** - * Get the length of time to wait before resetting the ike session when a data stall is - * suspected. + * Get the length of time to wait before perform data stall recovery when the validation + * result is bad. */ - public long getDataStallResetSessionSeconds(int count) { - if (count >= DATA_STALL_RESET_DELAYS_SEC.length) { - return DATA_STALL_RESET_DELAYS_SEC[DATA_STALL_RESET_DELAYS_SEC.length - 1]; + public long getValidationFailRecoverySeconds(int count) { + if (count >= DATA_STALL_RECOVERY_DELAYS_SEC.length) { + return DATA_STALL_RECOVERY_DELAYS_SEC[DATA_STALL_RECOVERY_DELAYS_SEC.length - 1]; } else { - return DATA_STALL_RESET_DELAYS_SEC[count]; + return DATA_STALL_RECOVERY_DELAYS_SEC[count]; } } @@ -1598,6 +1604,8 @@ public class Vpn { return network; } + // TODO : this is not synchronized(this) but reads from mConfig, which is dangerous + // This file makes an effort to avoid partly initializing mConfig, but this is still not great private LinkProperties makeLinkProperties() { // The design of disabling IPv6 is only enabled for IKEv2 VPN because it needs additional // logic to handle IPv6 only VPN, and the IPv6 only VPN may be restarted when its MTU @@ -1679,6 +1687,7 @@ public class Vpn { * registering a new NetworkAgent. This is not always possible if the new VPN configuration * has certain changes, in which case this method would just return {@code false}. */ + // TODO : this method is not synchronized(this) but reads from mConfig private boolean updateLinkPropertiesInPlaceIfPossible(NetworkAgent agent, VpnConfig oldConfig) { // NetworkAgentConfig cannot be updated without registering a new NetworkAgent. // Strictly speaking, bypassability is affected by lockdown and therefore it's possible @@ -2269,7 +2278,12 @@ public class Vpn { */ public synchronized VpnConfig getVpnConfig() { enforceControlPermission(); - return mConfig; + // Constructor of VpnConfig cannot take a null parameter. Return null directly if mConfig is + // null + if (mConfig == null) return null; + // mConfig is guarded by "this" and can be modified by another thread as soon as + // this method returns, so this method must return a copy. + return new VpnConfig(mConfig); } @Deprecated @@ -2315,6 +2329,7 @@ public class Vpn { } }; + @GuardedBy("this") private void cleanupVpnStateLocked() { mStatusIntent = null; resetNetworkCapabilities(); @@ -2837,9 +2852,7 @@ public class Vpn { } final boolean isLegacyVpn = mVpnRunner instanceof LegacyVpnRunner; - mVpnRunner.exit(); - mVpnRunner = null; // LegacyVpn uses daemons that must be shut down before new ones are brought up. // The same limitation does not apply to Platform VPNs. @@ -3044,7 +3057,6 @@ public class Vpn { @Nullable private IkeSessionWrapper mSession; @Nullable private IkeSessionConnectionInfo mIkeConnectionInfo; - @Nullable private VpnConnectivityDiagnosticsCallback mDiagnosticsCallback; // mMobikeEnabled can only be updated after IKE AUTH is finished. private boolean mMobikeEnabled = false; @@ -3055,7 +3067,7 @@ public class Vpn { * <p>This variable controls the retry delay, and is reset when the VPN pass network * validation. */ - private int mDataStallRetryCount = 0; + private int mValidationFailRetryCount = 0; /** * The number of attempts since the last successful connection. @@ -3084,6 +3096,7 @@ public class Vpn { } }; + // GuardedBy("Vpn.this") (annotation can't be applied to constructor) IkeV2VpnRunner( @NonNull Ikev2VpnProfile profile, @NonNull ScheduledThreadPoolExecutor executor) { super(TAG); @@ -3136,15 +3149,6 @@ public class Vpn { mConnectivityManager.registerSystemDefaultNetworkCallback(mNetworkCallback, new Handler(mLooper)); } - - // DiagnosticsCallback may return more than one alive VPNs, but VPN will filter based on - // Network object. - final NetworkRequest diagRequest = new NetworkRequest.Builder() - .addTransportType(TRANSPORT_VPN) - .removeCapability(NET_CAPABILITY_NOT_VPN).build(); - mDiagnosticsCallback = new VpnConnectivityDiagnosticsCallback(); - mConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback( - diagRequest, mExecutor, mDiagnosticsCallback); } private boolean isActiveNetwork(@Nullable Network network) { @@ -3710,11 +3714,14 @@ public class Vpn { } public void updateVpnTransportInfoAndNetCap(int keepaliveDelaySec) { - final VpnTransportInfo info = new VpnTransportInfo( - getActiveVpnType(), - mConfig.session, - mConfig.allowBypass && !mLockdown, - areLongLivedTcpConnectionsExpensive(keepaliveDelaySec)); + final VpnTransportInfo info; + synchronized (Vpn.this) { + info = new VpnTransportInfo( + getActiveVpnType(), + mConfig.session, + mConfig.allowBypass && !mLockdown, + areLongLivedTcpConnectionsExpensive(keepaliveDelaySec)); + } final boolean ncUpdateRequired = !info.equals(mNetworkCapabilities.getTransportInfo()); if (ncUpdateRequired) { mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities) @@ -3875,39 +3882,12 @@ public class Vpn { } } - class VpnConnectivityDiagnosticsCallback - extends ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback { - // The callback runs in the executor thread. - @Override - public void onDataStallSuspected( - ConnectivityDiagnosticsManager.DataStallReport report) { - synchronized (Vpn.this) { - // Ignore stale runner. - if (mVpnRunner != Vpn.IkeV2VpnRunner.this) return; - - // Handle the report only for current VPN network. If data stall is already - // reported, ignoring the other reports. It means that the stall is not - // recovered by MOBIKE and should be on the way to reset the ike session. - if (mNetworkAgent != null - && mNetworkAgent.getNetwork().equals(report.getNetwork()) - && !mDataStallSuspected) { - Log.d(TAG, "Data stall suspected"); - - // Trigger MOBIKE. - maybeMigrateIkeSessionAndUpdateVpnTransportInfo(mActiveNetwork); - mDataStallSuspected = true; - } - } - } - } - public void onValidationStatus(int status) { mEventChanges.log("[Validation] validation status " + status); if (status == NetworkAgent.VALIDATION_STATUS_VALID) { // No data stall now. Reset it. mExecutor.execute(() -> { - mDataStallSuspected = false; - mDataStallRetryCount = 0; + mValidationFailRetryCount = 0; if (mScheduledHandleDataStallFuture != null) { Log.d(TAG, "Recovered from stall. Cancel pending reset action."); mScheduledHandleDataStallFuture.cancel(false /* mayInterruptIfRunning */); @@ -3918,8 +3898,21 @@ public class Vpn { // Skip other invalid status if the scheduled recovery exists. if (mScheduledHandleDataStallFuture != null) return; + if (mValidationFailRetryCount < MAX_MOBIKE_RECOVERY_ATTEMPT) { + Log.d(TAG, "Validation failed"); + + // Trigger MOBIKE to recover first. + mExecutor.schedule(() -> { + maybeMigrateIkeSessionAndUpdateVpnTransportInfo(mActiveNetwork); + }, mDeps.getValidationFailRecoverySeconds(mValidationFailRetryCount++), + TimeUnit.SECONDS); + return; + } + + // Data stall is not recovered by MOBIKE. Try to reset session to recover it. mScheduledHandleDataStallFuture = mExecutor.schedule(() -> { - if (mDataStallSuspected) { + // Only perform the recovery when the network is still bad. + if (mValidationFailRetryCount > 0) { Log.d(TAG, "Reset session to recover stalled network"); // This will reset old state if it exists. startIkeSession(mActiveNetwork); @@ -3928,7 +3921,9 @@ public class Vpn { // Reset mScheduledHandleDataStallFuture since it's already run on executor // thread. mScheduledHandleDataStallFuture = null; - }, mDeps.getDataStallResetSessionSeconds(mDataStallRetryCount++), TimeUnit.SECONDS); + // TODO: compute the delay based on the last recovery timestamp + }, mDeps.getValidationFailRecoverySeconds(mValidationFailRetryCount++), + TimeUnit.SECONDS); } } @@ -4220,7 +4215,7 @@ public class Vpn { * consistency of the Ikev2VpnRunner fields. */ private void disconnectVpnRunner() { - mEventChanges.log("[VPNRunner] Disconnect runner, underlying network" + mActiveNetwork); + mEventChanges.log("[VPNRunner] Disconnect runner, underlying net " + mActiveNetwork); mActiveNetwork = null; mUnderlyingNetworkCapabilities = null; mUnderlyingLinkProperties = null; @@ -4231,8 +4226,6 @@ public class Vpn { mCarrierConfigManager.unregisterCarrierConfigChangeListener( mCarrierConfigChangeListener); mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); - mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback( - mDiagnosticsCallback); clearVpnNetworkPreference(mSessionKey); mExecutor.shutdown(); @@ -4293,6 +4286,7 @@ public class Vpn { } }; + // GuardedBy("Vpn.this") (annotation can't be applied to constructor) LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd, VpnProfile profile) { super(TAG); if (racoon == null && mtpd == null) { @@ -4500,46 +4494,46 @@ public class Vpn { } // Set the interface and the addresses in the config. - mConfig.interfaze = parameters[0].trim(); + synchronized (Vpn.this) { + mConfig.interfaze = parameters[0].trim(); - mConfig.addLegacyAddresses(parameters[1]); - // Set the routes if they are not set in the config. - if (mConfig.routes == null || mConfig.routes.isEmpty()) { - mConfig.addLegacyRoutes(parameters[2]); - } + mConfig.addLegacyAddresses(parameters[1]); + // Set the routes if they are not set in the config. + if (mConfig.routes == null || mConfig.routes.isEmpty()) { + mConfig.addLegacyRoutes(parameters[2]); + } - // Set the DNS servers if they are not set in the config. - if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) { - String dnsServers = parameters[3].trim(); - if (!dnsServers.isEmpty()) { - mConfig.dnsServers = Arrays.asList(dnsServers.split(" ")); + // Set the DNS servers if they are not set in the config. + if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) { + String dnsServers = parameters[3].trim(); + if (!dnsServers.isEmpty()) { + mConfig.dnsServers = Arrays.asList(dnsServers.split(" ")); + } } - } - // Set the search domains if they are not set in the config. - if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) { - String searchDomains = parameters[4].trim(); - if (!searchDomains.isEmpty()) { - mConfig.searchDomains = Arrays.asList(searchDomains.split(" ")); + // Set the search domains if they are not set in the config. + if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) { + String searchDomains = parameters[4].trim(); + if (!searchDomains.isEmpty()) { + mConfig.searchDomains = Arrays.asList(searchDomains.split(" ")); + } } - } - // Add a throw route for the VPN server endpoint, if one was specified. - if (endpointAddress instanceof Inet4Address) { - mConfig.routes.add(new RouteInfo( - new IpPrefix(endpointAddress, 32), null /*gateway*/, - null /*iface*/, RTN_THROW)); - } else if (endpointAddress instanceof Inet6Address) { - mConfig.routes.add(new RouteInfo( - new IpPrefix(endpointAddress, 128), null /*gateway*/, - null /*iface*/, RTN_THROW)); - } else { - Log.e(TAG, "Unknown IP address family for VPN endpoint: " - + endpointAddress); - } + // Add a throw route for the VPN server endpoint, if one was specified. + if (endpointAddress instanceof Inet4Address) { + mConfig.routes.add(new RouteInfo( + new IpPrefix(endpointAddress, 32), null /*gateway*/, + null /*iface*/, RTN_THROW)); + } else if (endpointAddress instanceof Inet6Address) { + mConfig.routes.add(new RouteInfo( + new IpPrefix(endpointAddress, 128), null /*gateway*/, + null /*iface*/, RTN_THROW)); + } else { + Log.e(TAG, "Unknown IP address family for VPN endpoint: " + + endpointAddress); + } - // Here is the last step and it must be done synchronously. - synchronized (Vpn.this) { + // Here is the last step and it must be done synchronously. // Set the start time mConfig.startTime = SystemClock.elapsedRealtime(); @@ -4773,25 +4767,26 @@ public class Vpn { try { // Build basic config - mConfig = new VpnConfig(); + final VpnConfig config = new VpnConfig(); if (VpnConfig.LEGACY_VPN.equals(packageName)) { - mConfig.legacy = true; - mConfig.session = profile.name; - mConfig.user = profile.key; + config.legacy = true; + config.session = profile.name; + config.user = profile.key; // TODO: Add support for configuring meteredness via Settings. Until then, use a // safe default. - mConfig.isMetered = true; + config.isMetered = true; } else { - mConfig.user = packageName; - mConfig.isMetered = profile.isMetered; + config.user = packageName; + config.isMetered = profile.isMetered; } - mConfig.startTime = SystemClock.elapsedRealtime(); - mConfig.proxyInfo = profile.proxy; - mConfig.requiresInternetValidation = profile.requiresInternetValidation; - mConfig.excludeLocalRoutes = profile.excludeLocalRoutes; - mConfig.allowBypass = profile.isBypassable; - mConfig.disallowedApplications = getAppExclusionList(mPackage); + config.startTime = SystemClock.elapsedRealtime(); + config.proxyInfo = profile.proxy; + config.requiresInternetValidation = profile.requiresInternetValidation; + config.excludeLocalRoutes = profile.excludeLocalRoutes; + config.allowBypass = profile.isBypassable; + config.disallowedApplications = getAppExclusionList(mPackage); + mConfig = config; switch (profile.type) { case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS: @@ -4805,6 +4800,7 @@ public class Vpn { mVpnRunner.start(); break; default: + mConfig = null; updateState(DetailedState.FAILED, "Invalid platform VPN type"); Log.d(TAG, "Unknown VPN profile type: " + profile.type); break; @@ -5216,7 +5212,7 @@ public class Vpn { pw.println("MOBIKE " + (runner.mMobikeEnabled ? "enabled" : "disabled")); pw.println("Profile: " + runner.mProfile); pw.println("Token: " + runner.mCurrentToken); - if (mDataStallSuspected) pw.println("Data stall suspected"); + pw.println("Validation failed retry count:" + runner.mValidationFailRetryCount); if (runner.mScheduledHandleDataStallFuture != null) { pw.println("Reset session scheduled"); } diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java index f84a58c5b06d..472c1f58dc8a 100644 --- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java +++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java @@ -22,7 +22,6 @@ import android.os.Environment; import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.SparseArray; -import android.view.Display; import android.view.DisplayAddress; import com.android.internal.annotations.VisibleForTesting; @@ -38,6 +37,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.math.BigInteger; import javax.xml.datatype.DatatypeConfigurationException; @@ -115,13 +115,16 @@ class DeviceStateToLayoutMap { Slog.i(TAG, "Display layout config not found: " + configFile); return; } - int leadDisplayId = Display.DEFAULT_DISPLAY; for (com.android.server.display.config.layout.Layout l : layouts.getLayout()) { final int state = l.getState().intValue(); final Layout layout = createLayout(state); for (com.android.server.display.config.layout.Display d: l.getDisplay()) { assert layout != null; int position = getPosition(d.getPosition()); + BigInteger leadDisplayPhysicalId = d.getLeadDisplayAddress(); + DisplayAddress leadDisplayAddress = leadDisplayPhysicalId == null ? null + : DisplayAddress.fromPhysicalDisplayId( + leadDisplayPhysicalId.longValue()); layout.createDisplayLocked( DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()), d.isDefaultDisplay(), @@ -129,11 +132,12 @@ class DeviceStateToLayoutMap { d.getDisplayGroup(), mIdProducer, position, - leadDisplayId, + leadDisplayAddress, d.getBrightnessThrottlingMapId(), d.getRefreshRateZoneId(), d.getRefreshRateThermalThrottlingMapId()); } + layout.postProcessLocked(); } } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) { Slog.e(TAG, "Encountered an error while reading/parsing display layout config file: " diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java index b55d7d5d9d3c..d9ec3de46b0e 100644 --- a/services/core/java/com/android/server/display/layout/Layout.java +++ b/services/core/java/com/android/server/display/layout/Layout.java @@ -22,6 +22,8 @@ import static com.android.server.display.layout.Layout.Display.POSITION_UNKNOWN; import android.annotation.NonNull; import android.annotation.Nullable; +import android.text.TextUtils; +import android.util.ArraySet; import android.util.Slog; import android.view.DisplayAddress; @@ -77,7 +79,7 @@ public class Layout { DisplayIdProducer idProducer) { createDisplayLocked(address, /* isDefault= */ true, /* isEnabled= */ true, DEFAULT_DISPLAY_GROUP_NAME, idProducer, POSITION_UNKNOWN, - NO_LEAD_DISPLAY, /* brightnessThrottlingMapId= */ null, + /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null); } @@ -90,19 +92,20 @@ public class Layout { * @param displayGroupName Name of the display group to which the display is assigned. * @param idProducer Produces the logical display id. * @param position Indicates the position this display is facing in this layout. - * @param leadDisplayId Display that this one follows (-1 if none). + * @param leadDisplayAddress Address of a display that this one follows ({@code null} if none). * @param brightnessThrottlingMapId Name of which brightness throttling policy should be used. * @param refreshRateZoneId Layout limited refresh rate zone name. * @param refreshRateThermalThrottlingMapId Name of which refresh rate throttling * policy should be used. - + * * @exception IllegalArgumentException When a default display owns a display group other than * DEFAULT_DISPLAY_GROUP. */ public void createDisplayLocked( @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled, - String displayGroupName, DisplayIdProducer idProducer, int position, int leadDisplayId, - String brightnessThrottlingMapId, @Nullable String refreshRateZoneId, + String displayGroupName, DisplayIdProducer idProducer, int position, + @Nullable DisplayAddress leadDisplayAddress, String brightnessThrottlingMapId, + @Nullable String refreshRateZoneId, @Nullable String refreshRateThermalThrottlingMapId) { if (contains(address)) { Slog.w(TAG, "Attempting to add second definition for display-device: " + address); @@ -115,21 +118,27 @@ public class Layout { return; } - // Assign a logical display ID and create the new display. - // Note that the logical display ID is saved into the layout, so when switching between - // different layouts, a logical display can be destroyed and later recreated with the - // same logical display ID. if (displayGroupName == null) { displayGroupName = DEFAULT_DISPLAY_GROUP_NAME; } if (isDefault && !displayGroupName.equals(DEFAULT_DISPLAY_GROUP_NAME)) { throw new IllegalArgumentException("Default display should own DEFAULT_DISPLAY_GROUP"); } + if (isDefault && leadDisplayAddress != null) { + throw new IllegalArgumentException("Default display cannot have a lead display"); + } + if (address.equals(leadDisplayAddress)) { + throw new IllegalArgumentException("Lead display address cannot be the same as display " + + " address"); + } + // Assign a logical display ID and create the new display. + // Note that the logical display ID is saved into the layout, so when switching between + // different layouts, a logical display can be destroyed and later recreated with the + // same logical display ID. final int logicalDisplayId = idProducer.getId(isDefault); - leadDisplayId = isDefault ? NO_LEAD_DISPLAY : leadDisplayId; final Display display = new Display(address, logicalDisplayId, isEnabled, displayGroupName, - brightnessThrottlingMapId, position, leadDisplayId, refreshRateZoneId, + brightnessThrottlingMapId, position, leadDisplayAddress, refreshRateZoneId, refreshRateThermalThrottlingMapId); mDisplays.add(display); @@ -146,6 +155,43 @@ public class Layout { } /** + * Applies post-processing to displays to make sure the information of each display is + * up-to-date. + * + * <p>At creation of a display, lead display is specified by display address. At post + * processing, we convert it to logical display ID. + */ + public void postProcessLocked() { + for (int i = 0; i < mDisplays.size(); i++) { + Display display = mDisplays.get(i); + if (display.getLogicalDisplayId() == DEFAULT_DISPLAY) { + display.setLeadDisplayId(NO_LEAD_DISPLAY); + continue; + } + DisplayAddress leadDisplayAddress = display.getLeadDisplayAddress(); + if (leadDisplayAddress == null) { + display.setLeadDisplayId(NO_LEAD_DISPLAY); + continue; + } + Display leadDisplay = getByAddress(leadDisplayAddress); + if (leadDisplay == null) { + throw new IllegalArgumentException("Cannot find a lead display whose address is " + + leadDisplayAddress); + } + if (!TextUtils.equals(display.getDisplayGroupName(), + leadDisplay.getDisplayGroupName())) { + throw new IllegalArgumentException("Lead display(" + leadDisplay + ") should be in " + + "the same display group of the display(" + display + ")"); + } + if (hasCyclicLeadDisplay(display)) { + throw new IllegalArgumentException("Display(" + display + ") has a cyclic lead " + + "display"); + } + display.setLeadDisplayId(leadDisplay.getLogicalDisplayId()); + } + } + + /** * @param address The address to check. * * @return True if the specified address is used in this layout. @@ -208,6 +254,20 @@ public class Layout { return mDisplays.size(); } + private boolean hasCyclicLeadDisplay(Display display) { + ArraySet<Display> visited = new ArraySet<>(); + + while (display != null) { + if (visited.contains(display)) { + return true; + } + visited.add(display); + DisplayAddress leadDisplayAddress = display.getLeadDisplayAddress(); + display = leadDisplayAddress == null ? null : getByAddress(leadDisplayAddress); + } + return false; + } + /** * Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s. */ @@ -240,8 +300,9 @@ public class Layout { @Nullable private final String mThermalBrightnessThrottlingMapId; - // The ID of the lead display that this display will follow in a layout. -1 means no lead. - private final int mLeadDisplayId; + // The address of the lead display that is specified in display-layout-configuration. + @Nullable + private final DisplayAddress mLeadDisplayAddress; // Refresh rate zone id for specific layout @Nullable @@ -250,9 +311,13 @@ public class Layout { @Nullable private final String mThermalRefreshRateThrottlingMapId; + // The ID of the lead display that this display will follow in a layout. -1 means no lead. + // This is determined using {@code mLeadDisplayAddress}. + private int mLeadDisplayId; + private Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled, @NonNull String displayGroupName, String brightnessThrottlingMapId, int position, - int leadDisplayId, @Nullable String refreshRateZoneId, + @Nullable DisplayAddress leadDisplayAddress, @Nullable String refreshRateZoneId, @Nullable String refreshRateThermalThrottlingMapId) { mAddress = address; mLogicalDisplayId = logicalDisplayId; @@ -260,9 +325,10 @@ public class Layout { mDisplayGroupName = displayGroupName; mPosition = position; mThermalBrightnessThrottlingMapId = brightnessThrottlingMapId; + mLeadDisplayAddress = leadDisplayAddress; mRefreshRateZoneId = refreshRateZoneId; mThermalRefreshRateThrottlingMapId = refreshRateThermalThrottlingMapId; - mLeadDisplayId = leadDisplayId; + mLeadDisplayId = NO_LEAD_DISPLAY; } @Override @@ -276,6 +342,7 @@ public class Layout { + ", mThermalBrightnessThrottlingMapId: " + mThermalBrightnessThrottlingMapId + ", mRefreshRateZoneId: " + mRefreshRateZoneId + ", mLeadDisplayId: " + mLeadDisplayId + + ", mLeadDisplayAddress: " + mLeadDisplayAddress + ", mThermalRefreshRateThrottlingMapId: " + mThermalRefreshRateThrottlingMapId + "}"; } @@ -297,6 +364,7 @@ public class Layout { otherDisplay.mThermalBrightnessThrottlingMapId) && Objects.equals(otherDisplay.mRefreshRateZoneId, this.mRefreshRateZoneId) && this.mLeadDisplayId == otherDisplay.mLeadDisplayId + && Objects.equals(mLeadDisplayAddress, otherDisplay.mLeadDisplayAddress) && Objects.equals(mThermalRefreshRateThrottlingMapId, otherDisplay.mThermalRefreshRateThrottlingMapId); } @@ -309,9 +377,10 @@ public class Layout { result = 31 * result + mLogicalDisplayId; result = 31 * result + mDisplayGroupName.hashCode(); result = 31 * result + mAddress.hashCode(); - result = 31 * result + mThermalBrightnessThrottlingMapId.hashCode(); + result = 31 * result + Objects.hashCode(mThermalBrightnessThrottlingMapId); result = 31 * result + Objects.hashCode(mRefreshRateZoneId); result = 31 * result + mLeadDisplayId; + result = 31 * result + Objects.hashCode(mLeadDisplayAddress); result = 31 * result + Objects.hashCode(mThermalRefreshRateThrottlingMapId); return result; } @@ -360,8 +429,20 @@ public class Layout { return mLeadDisplayId; } + /** + * @return Display address of the display that this one follows. + */ + @Nullable + public DisplayAddress getLeadDisplayAddress() { + return mLeadDisplayAddress; + } + public String getRefreshRateThermalThrottlingMapId() { return mThermalRefreshRateThrottlingMapId; } + + private void setLeadDisplayId(int id) { + mLeadDisplayId = id; + } } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index e0e6410a965d..d093393e8d86 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -4443,7 +4443,7 @@ public class NotificationManagerService extends SystemService { // Remove background token before returning notification to untrusted app, this // ensures the app isn't able to perform background operations that are // associated with notification interactions. - notification.setAllowlistToken(null); + notification.clearAllowlistToken(); return new StatusBarNotification( sbn.getPackageName(), sbn.getOpPkg(), diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 9402fca9574f..279a48084252 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -91,6 +91,7 @@ import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityManagerInternal; @@ -632,6 +633,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { SettingsObserver mSettingsObserver; ModifierShortcutManager mModifierShortcutManager; + /** Currently fully consumed key codes per device */ + private final SparseArray<Set<Integer>> mConsumedKeysForDevice = new SparseArray<>(); PowerManager.WakeLock mBroadcastWakeLock; PowerManager.WakeLock mPowerKeyWakeLock; boolean mHavePendingMediaKeyRepeatWithWakeLock; @@ -1817,7 +1820,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mDisplayId = displayId; } - int handleHomeButton(IBinder focusedToken, KeyEvent event) { + boolean handleHomeButton(IBinder focusedToken, KeyEvent event) { final boolean keyguardOn = keyguardOn(); final int repeatCount = event.getRepeatCount(); final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; @@ -1838,12 +1841,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { mHomePressed = false; if (mHomeConsumed) { mHomeConsumed = false; - return -1; + return true; } if (canceled) { Log.i(TAG, "Ignoring HOME; event canceled."); - return -1; + return true; } // Delay handling home if a double-tap is possible. @@ -1855,13 +1858,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { mHomeDoubleTapPending = true; mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable, ViewConfiguration.getDoubleTapTimeout()); - return -1; + return true; } } // Post to main thread to avoid blocking input pipeline. mHandler.post(() -> handleShortPressOnHome(mDisplayId)); - return -1; + return true; } final KeyInterceptionInfo info = @@ -1873,12 +1876,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { || (info.layoutParamsType == TYPE_NOTIFICATION_SHADE && isKeyguardShowing())) { // the "app" is keyguard, so give it the key - return 0; + return false; } for (int t : WINDOW_TYPES_WHERE_HOME_DOESNT_WORK) { if (info.layoutParamsType == t) { // don't do anything, but also don't pass it to the app - return -1; + return true; } } } @@ -1903,7 +1906,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { event.getEventTime())); } } - return -1; + return true; } private void handleDoubleTapOnHome() { @@ -2949,24 +2952,21 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event, int policyFlags) { - final boolean keyguardOn = keyguardOn(); final int keyCode = event.getKeyCode(); - final int repeatCount = event.getRepeatCount(); - final int metaState = event.getMetaState(); final int flags = event.getFlags(); - final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; - final boolean canceled = event.isCanceled(); - final int displayId = event.getDisplayId(); - final long key_consumed = -1; - final long key_not_consumed = 0; + final long keyConsumed = -1; + final long keyNotConsumed = 0; + final int deviceId = event.getDeviceId(); if (DEBUG_INPUT) { - Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount=" - + repeatCount + " keyguardOn=" + keyguardOn + " canceled=" + canceled); + Log.d(TAG, + "interceptKeyTi keyCode=" + keyCode + " action=" + event.getAction() + + " repeatCount=" + event.getRepeatCount() + " keyguardOn=" + + keyguardOn() + " canceled=" + event.isCanceled()); } if (mKeyCombinationManager.isKeyConsumed(event)) { - return key_consumed; + return keyConsumed; } if ((flags & KeyEvent.FLAG_FALLBACK) == 0) { @@ -2977,8 +2977,54 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - // Cancel any pending meta actions if we see any other keys being pressed between the down - // of the meta key and its corresponding up. + Set<Integer> consumedKeys = mConsumedKeysForDevice.get(deviceId); + if (consumedKeys == null) { + consumedKeys = new HashSet<>(); + mConsumedKeysForDevice.put(deviceId, consumedKeys); + } + + if (interceptSystemKeysAndShortcuts(focusedToken, event) + && event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { + consumedKeys.add(keyCode); + return keyConsumed; + } + + boolean needToConsumeKey = consumedKeys.contains(keyCode); + if (event.getAction() == KeyEvent.ACTION_UP || event.isCanceled()) { + consumedKeys.remove(keyCode); + if (consumedKeys.isEmpty()) { + mConsumedKeysForDevice.remove(deviceId); + } + } + + return needToConsumeKey ? keyConsumed : keyNotConsumed; + } + + // You can only start consuming the key gesture if ACTION_DOWN and repeat count + // is 0. If you start intercepting the key halfway, then key will not be consumed + // and will be sent to apps for processing too. + // e.g. If a certain combination is only handled on ACTION_UP (i.e. + // interceptShortcut() returns true only for ACTION_UP), then since we already + // sent the ACTION_DOWN events to the application, we MUST also send the + // ACTION_UP to the application. + // So, to ensure that your intercept logic works properly, and we don't send any + // conflicting events to application, make sure to consume the event on + // ACTION_DOWN even if you want to do something on ACTION_UP. This is essential + // to maintain event parity and to not have incomplete key gestures. + @SuppressLint("MissingPermission") + private boolean interceptSystemKeysAndShortcuts(IBinder focusedToken, KeyEvent event) { + final boolean keyguardOn = keyguardOn(); + final int keyCode = event.getKeyCode(); + final int repeatCount = event.getRepeatCount(); + final int metaState = event.getMetaState(); + final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; + final boolean canceled = event.isCanceled(); + final int displayId = event.getDisplayId(); + final int deviceId = event.getDeviceId(); + final boolean firstDown = down && repeatCount == 0; + + // Cancel any pending meta actions if we see any other keys being pressed between the + // down of the meta key and its corresponding up. if (mPendingMetaAction && !KeyEvent.isMetaKey(keyCode)) { mPendingMetaAction = false; } @@ -2992,50 +3038,49 @@ public class PhoneWindowManager implements WindowManagerPolicy { dismissKeyboardShortcutsMenu(); mPendingMetaAction = false; mPendingCapsLockToggle = false; - return key_consumed; + return true; } } - switch(keyCode) { + switch (keyCode) { case KeyEvent.KEYCODE_HOME: - logKeyboardSystemsEvent(event, FrameworkStatsLog - .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME); + logKeyboardSystemsEvent(event, + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME); return handleHomeShortcuts(displayId, focusedToken, event); case KeyEvent.KEYCODE_MENU: // Hijack modified menu keys for debugging features final int chordBug = KeyEvent.META_SHIFT_ON; - if (down && repeatCount == 0) { - if (mEnableShiftMenuBugReports && (metaState & chordBug) == chordBug) { - Intent intent = new Intent(Intent.ACTION_BUG_REPORT); - mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, - null, null, null, 0, null, null); - return key_consumed; - } + if (mEnableShiftMenuBugReports && firstDown + && (metaState & chordBug) == chordBug) { + Intent intent = new Intent(Intent.ACTION_BUG_REPORT); + mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, + null, null, null, 0, null, null); + return true; } break; case KeyEvent.KEYCODE_RECENT_APPS: - if (down && repeatCount == 0) { + if (firstDown) { showRecentApps(false /* triggeredFromAltTab */); - logKeyboardSystemsEvent(event, FrameworkStatsLog - .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS); + logKeyboardSystemsEvent(event, + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS); } - return key_consumed; + return true; case KeyEvent.KEYCODE_APP_SWITCH: if (!keyguardOn) { - if (down && repeatCount == 0) { + if (firstDown) { preloadRecentApps(); } else if (!down) { toggleRecentApps(); } } - return key_consumed; + return true; case KeyEvent.KEYCODE_A: - if (down && event.isMetaPressed()) { + if (firstDown && event.isMetaPressed()) { launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD, - event.getDeviceId(), - event.getEventTime(), AssistUtils.INVOCATION_TYPE_UNKNOWN); - return key_consumed; + deviceId, event.getEventTime(), + AssistUtils.INVOCATION_TYPE_UNKNOWN); + return true; } break; case KeyEvent.KEYCODE_H: @@ -3045,73 +3090,73 @@ public class PhoneWindowManager implements WindowManagerPolicy { } break; case KeyEvent.KEYCODE_I: - if (down && event.isMetaPressed()) { + if (firstDown && event.isMetaPressed()) { showSystemSettings(); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_L: - if (down && event.isMetaPressed() && repeatCount == 0) { + if (firstDown && event.isMetaPressed()) { lockNow(null /* options */); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_N: - if (down && event.isMetaPressed()) { + if (firstDown && event.isMetaPressed()) { if (event.isCtrlPressed()) { sendSystemKeyToStatusBarAsync(event); } else { toggleNotificationPanel(); } - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_S: - if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) { + if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_T: - if (down && event.isMetaPressed()) { + if (firstDown && event.isMetaPressed()) { toggleTaskbar(); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_DPAD_UP: - if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) { + if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); if (statusbar != null) { statusbar.goToFullscreenFromSplit(); + return true; } - return key_consumed; } break; case KeyEvent.KEYCODE_DPAD_LEFT: - if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) { + if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { enterStageSplitFromRunningApp(true /* leftOrTop */); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_DPAD_RIGHT: - if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) { + if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { enterStageSplitFromRunningApp(false /* leftOrTop */); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_SLASH: - if (down && repeatCount == 0 && event.isMetaPressed() && !keyguardOn) { + if (firstDown && event.isMetaPressed() && !keyguardOn) { toggleKeyboardShortcutsMenu(event.getDeviceId()); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_ASSIST: Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing"); - return key_consumed; + return true; case KeyEvent.KEYCODE_VOICE_ASSIST: Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in" + " interceptKeyBeforeQueueing"); - return key_consumed; + return true; case KeyEvent.KEYCODE_VIDEO_APP_1: case KeyEvent.KEYCODE_VIDEO_APP_2: case KeyEvent.KEYCODE_VIDEO_APP_3: @@ -3129,7 +3174,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_DEMO_APP_3: case KeyEvent.KEYCODE_DEMO_APP_4: Slog.wtf(TAG, "KEYCODE_APP_X should be handled in interceptKeyBeforeQueueing"); - return key_consumed; + return true; case KeyEvent.KEYCODE_BRIGHTNESS_UP: case KeyEvent.KEYCODE_BRIGHTNESS_DOWN: if (down) { @@ -3169,20 +3214,20 @@ public class PhoneWindowManager implements WindowManagerPolicy { startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG), UserHandle.CURRENT_OR_SELF); } - return key_consumed; + return true; case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN: if (down) { mInputManagerInternal.decrementKeyboardBacklight(event.getDeviceId()); } - return key_consumed; + return true; case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP: if (down) { mInputManagerInternal.incrementKeyboardBacklight(event.getDeviceId()); } - return key_consumed; + return true; case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE: // TODO: Add logic - return key_consumed; + return true; case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_MUTE: @@ -3190,7 +3235,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // On TVs or when the configuration is enabled, volume keys never // go to the foreground app. dispatchDirectAudioEvent(event); - return key_consumed; + return true; } // If the device is in VR mode and keys are "internal" (e.g. on the side of the @@ -3199,26 +3244,23 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mDefaultDisplayPolicy.isPersistentVrModeEnabled()) { final InputDevice d = event.getDevice(); if (d != null && !d.isExternal()) { - return key_consumed; + return true; } } break; case KeyEvent.KEYCODE_TAB: - if (down && event.isMetaPressed()) { - if (!keyguardOn && isUserSetupComplete()) { + if (firstDown && !keyguardOn && isUserSetupComplete()) { + if (event.isMetaPressed()) { showRecentApps(false); - return key_consumed; - } - } else if (down && repeatCount == 0) { - // Display task switcher for ALT-TAB. - if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) { + return true; + } else if (mRecentAppsHeldModifiers == 0) { final int shiftlessModifiers = event.getModifiers() & ~KeyEvent.META_SHIFT_MASK; if (KeyEvent.metaStateHasModifiers( shiftlessModifiers, KeyEvent.META_ALT_ON)) { mRecentAppsHeldModifiers = shiftlessModifiers; showRecentApps(true); - return key_consumed; + return true; } } } @@ -3230,18 +3272,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { msg.setAsynchronous(true); msg.sendToTarget(); } - return key_consumed; + return true; case KeyEvent.KEYCODE_NOTIFICATION: if (!down) { toggleNotificationPanel(); } - return key_consumed; + return true; case KeyEvent.KEYCODE_SEARCH: - if (down && repeatCount == 0 && !keyguardOn()) { - switch(mSearchKeyBehavior) { + if (firstDown && !keyguardOn) { + switch (mSearchKeyBehavior) { case SEARCH_BEHAVIOR_TARGET_ACTIVITY: { launchTargetSearchActivity(); - return key_consumed; + return true; } case SEARCH_BEHAVIOR_DEFAULT_SEARCH: default: @@ -3250,21 +3292,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { } break; case KeyEvent.KEYCODE_LANGUAGE_SWITCH: - if (down && repeatCount == 0) { + if (firstDown) { int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; sendSwitchKeyboardLayout(event, direction); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_SPACE: // Handle keyboard layout switching. (META + SPACE) - if ((metaState & KeyEvent.META_META_MASK) == 0) { - return key_not_consumed; - } - if (down && repeatCount == 0) { + if (firstDown && event.isMetaPressed()) { int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; sendSwitchKeyboardLayout(event, direction); - return key_consumed; + return true; } break; case KeyEvent.KEYCODE_META_LEFT: @@ -3289,7 +3328,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPendingMetaAction = false; } } - return key_consumed; + return true; case KeyEvent.KEYCODE_ALT_LEFT: case KeyEvent.KEYCODE_ALT_RIGHT: if (down) { @@ -3305,14 +3344,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { && (metaState & mRecentAppsHeldModifiers) == 0) { mRecentAppsHeldModifiers = 0; hideRecentApps(true, false); - return key_consumed; + return true; } // Toggle Caps Lock on META-ALT. if (mPendingCapsLockToggle) { mInputManagerInternal.toggleCapsLock(event.getDeviceId()); mPendingCapsLockToggle = false; - return key_consumed; + return true; } } break; @@ -3322,24 +3361,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL: Slog.wtf(TAG, "KEYCODE_STYLUS_BUTTON_* should be handled in" + " interceptKeyBeforeQueueing"); - return key_consumed; + return true; } - if (isValidGlobalKey(keyCode) && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) { - return key_consumed; + return true; } // Reserve all the META modifier combos for system behavior - if ((metaState & KeyEvent.META_META_ON) != 0) { - return key_consumed; - } - - // Let the application handle the key. - return key_not_consumed; + return (metaState & KeyEvent.META_META_ON) != 0; } - private int handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) { + private boolean handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) { // First we always handle the home key here, so applications // can never break it, although if keyguard is on, we do let // it handle it, because that gives us the correct 5 second diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java index 6a7e76411fee..2ecbf8a25ed9 100644 --- a/services/core/java/com/android/server/wm/ContentRecorder.java +++ b/services/core/java/com/android/server/wm/ContentRecorder.java @@ -166,7 +166,7 @@ final class ContentRecorder implements WindowContainerListener { + "%d to new bounds %s and/or orientation %d.", mDisplayContent.getDisplayId(), recordedContentBounds, recordedContentOrientation); - updateMirroredSurface(mDisplayContent.mWmService.mTransactionFactory.get(), + updateMirroredSurface(mRecordedWindowContainer.getSyncTransaction(), recordedContentBounds, surfaceSize); } else { // If the surface removed, do nothing. We will handle this via onDisplayChanged @@ -325,6 +325,7 @@ final class ContentRecorder implements WindowContainerListener { .reparent(mDisplayContent.getOverlayLayer(), null); // Retrieve the size of the DisplayArea to mirror. updateMirroredSurface(transaction, mRecordedWindowContainer.getBounds(), surfaceSize); + transaction.apply(); // Notify the client about the visibility of the mirrored region, now that we have begun // capture. @@ -481,8 +482,7 @@ final class ContentRecorder implements WindowContainerListener { .setMatrix(mRecordedSurface, scale, 0 /* dtdx */, 0 /* dtdy */, scale) // Position needs to be updated when the mirrored DisplayArea has changed, since // the content will no longer be centered in the output surface. - .setPosition(mRecordedSurface, shiftedX /* x */, shiftedY /* y */) - .apply(); + .setPosition(mRecordedSurface, shiftedX /* x */, shiftedY /* y */); mLastRecordedBounds = new Rect(recordedContentBounds); // Request to notify the client about the resize. mMediaProjectionManager.notifyActiveProjectionCapturedContentResized( diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java index 2b6b62e0d81a..4180cd2298ea 100644 --- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java @@ -39,10 +39,11 @@ public class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { TAG_WITH_CLASS_NAME ? "DesktopModeLaunchParamsModifier" : TAG_ATM; private static final boolean DEBUG = false; - // Desktop mode feature flag. - static final boolean DESKTOP_MODE_SUPPORTED = SystemProperties.getBoolean( - "persist.wm.debug.desktop_mode", false) || SystemProperties.getBoolean( - "persist.wm.debug.desktop_mode_2", false); + // Desktop mode feature flags. + private static final boolean DESKTOP_MODE_PROTO1_SUPPORTED = + SystemProperties.getBoolean("persist.wm.debug.desktop_mode", false); + private static final boolean DESKTOP_MODE_PROTO2_SUPPORTED = + SystemProperties.getBoolean("persist.wm.debug.desktop_mode_2", false); // Override default freeform task width when desktop mode is enabled. In dips. private static final int DESKTOP_MODE_DEFAULT_WIDTH_DP = SystemProperties.getInt( "persist.wm.debug.desktop_mode.default_width", 840); @@ -76,22 +77,37 @@ public class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { appendLog("task null, skipping"); return RESULT_SKIP; } - if (phase != PHASE_BOUNDS) { - appendLog("not in bounds phase, skipping"); - return RESULT_SKIP; - } if (!task.isActivityTypeStandardOrUndefined()) { appendLog("not standard or undefined activity type, skipping"); return RESULT_SKIP; } - if (!currentParams.mBounds.isEmpty()) { - appendLog("currentParams has bounds set, not overriding"); + if (phase < PHASE_WINDOWING_MODE) { + appendLog("not in windowing mode or bounds phase, skipping"); return RESULT_SKIP; } // Copy over any values outParams.set(currentParams); + // In Proto2, trampoline task launches of an existing background task can result in the + // previous windowing mode to be restored even if the desktop mode state has changed. + // Let task launches inherit the windowing mode from the source task if available, which + // should have the desired windowing mode set by WM Shell. See b/286929122. + if (DESKTOP_MODE_PROTO2_SUPPORTED && source != null && source.getTask() != null) { + final Task sourceTask = source.getTask(); + outParams.mWindowingMode = sourceTask.getWindowingMode(); + appendLog("inherit-from-source=" + outParams.mWindowingMode); + } + + if (phase == PHASE_WINDOWING_MODE) { + return RESULT_DONE; + } + + if (!currentParams.mBounds.isEmpty()) { + appendLog("currentParams has bounds set, not overriding"); + return RESULT_SKIP; + } + // Update width and height with default desktop mode values float density = (float) task.getConfiguration().densityDpi / DENSITY_DEFAULT; final int width = (int) (DESKTOP_MODE_DEFAULT_WIDTH_DP * density + 0.5f); @@ -123,4 +139,9 @@ public class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { private void outputLog() { if (DEBUG) Slog.d(TAG, mLogBuilder.toString()); } + + /** Whether desktop mode is supported. */ + static boolean isDesktopModeSupported() { + return DESKTOP_MODE_PROTO1_SUPPORTED || DESKTOP_MODE_PROTO2_SUPPORTED; + } } diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java index e74e5787ef5a..91bb8d007948 100644 --- a/services/core/java/com/android/server/wm/LaunchParamsController.java +++ b/services/core/java/com/android/server/wm/LaunchParamsController.java @@ -64,7 +64,7 @@ class LaunchParamsController { void registerDefaultModifiers(ActivityTaskSupervisor supervisor) { // {@link TaskLaunchParamsModifier} handles window layout preferences. registerModifier(new TaskLaunchParamsModifier(supervisor)); - if (DesktopModeLaunchParamsModifier.DESKTOP_MODE_SUPPORTED) { + if (DesktopModeLaunchParamsModifier.isDesktopModeSupported()) { // {@link DesktopModeLaunchParamsModifier} handles default task size changes registerModifier(new DesktopModeLaunchParamsModifier()); } diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java index bf511adf0bf9..2394da91684d 100644 --- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java +++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java @@ -433,7 +433,7 @@ class LaunchParamsPersister { final byte[] data = saveParamsToXml(); final File launchParamFolder = getLaunchParamFolder(mUserId); - if (!launchParamFolder.isDirectory() && !launchParamFolder.mkdirs()) { + if (!launchParamFolder.isDirectory() && !launchParamFolder.mkdir()) { Slog.w(TAG, "Failed to create folder for " + mUserId); return; } diff --git a/services/core/java/com/android/server/wm/TaskPersister.java b/services/core/java/com/android/server/wm/TaskPersister.java index 29c192cc7c48..f882b9b18453 100644 --- a/services/core/java/com/android/server/wm/TaskPersister.java +++ b/services/core/java/com/android/server/wm/TaskPersister.java @@ -509,7 +509,7 @@ public class TaskPersister implements PersisterQueue.Listener { private static boolean createParentDirectory(String filePath) { File parentDir = new File(filePath).getParentFile(); - return parentDir.exists() || parentDir.mkdirs(); + return parentDir.isDirectory() || parentDir.mkdir(); } private static class TaskWriteQueueItem implements PersisterQueue.WriteQueueItem { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 5b3bbd50cb2d..db3b267529e6 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -619,6 +619,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (!isInTransientHide(wc)) { mSyncEngine.addToSyncSet(mSyncId, wc); } + if (wc.asWindowToken() != null && wc.asWindowToken().mRoundedCornerOverlay) { + // Only need to sync the transaction (SyncSet) without ChangeInfo because cutout and + // rounded corner overlay never need animations. Especially their surfaces may be put + // in root (null, see WindowToken#makeSurface()) that cannot reparent. + return; + } ChangeInfo info = mChanges.get(wc); if (info == null) { info = new ChangeInfo(wc); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 4bc4c266c114..457a555416c1 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -81,7 +81,9 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.Debug; import android.os.IBinder; +import android.os.RemoteException; import android.os.Trace; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Pair; import android.util.Pools; @@ -174,6 +176,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< */ protected SparseArray<InsetsSourceProvider> mInsetsSourceProviders = null; + @Nullable + private ArrayMap<IBinder, DeathRecipient> mInsetsOwnerDeathRecipientMap; + // List of children for this window container. List is in z-order as the children appear on // screen with the top-most window container at the tail of the list. protected final WindowList<E> mChildren = new WindowList<E>(); @@ -419,11 +424,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * Adds an {@link InsetsFrameProvider} which describes what insets should be provided to * this {@link WindowContainer} and its children. * - * @param provider describes the insets types and the frames. + * @param provider describes the insets type and the frame. + * @param owner owns the insets source which only exists when the owner is alive. */ - void addLocalInsetsFrameProvider(InsetsFrameProvider provider) { - if (provider == null) { - throw new IllegalArgumentException("Insets type not specified."); + void addLocalInsetsFrameProvider(InsetsFrameProvider provider, IBinder owner) { + if (provider == null || owner == null) { + throw new IllegalArgumentException("Insets provider or owner not specified."); } if (mDisplayContent == null) { // This is possible this container is detached when WM shell is responding to a previous @@ -432,10 +438,26 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< Slog.w(TAG, "Can't add insets frame provider when detached. " + this); return; } + + if (mInsetsOwnerDeathRecipientMap == null) { + mInsetsOwnerDeathRecipientMap = new ArrayMap<>(); + } + DeathRecipient deathRecipient = mInsetsOwnerDeathRecipientMap.get(owner); + if (deathRecipient == null) { + deathRecipient = new DeathRecipient(owner); + try { + owner.linkToDeath(deathRecipient, 0); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to add source for " + provider + " since the owner has died."); + return; + } + mInsetsOwnerDeathRecipientMap.put(owner, deathRecipient); + } + final int id = provider.getId(); + deathRecipient.addSourceId(id); if (mLocalInsetsSources == null) { mLocalInsetsSources = new SparseArray<>(); } - final int id = provider.getId(); if (mLocalInsetsSources.get(id) != null) { if (DEBUG) { Slog.d(TAG, "The local insets source for this " + provider @@ -448,27 +470,77 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mDisplayContent.getInsetsStateController().updateAboveInsetsState(true); } - void removeLocalInsetsFrameProvider(InsetsFrameProvider provider) { - if (provider == null) { - throw new IllegalArgumentException("Insets type not specified."); + private class DeathRecipient implements IBinder.DeathRecipient { + + private final IBinder mOwner; + private final ArraySet<Integer> mSourceIds = new ArraySet<>(); + + DeathRecipient(IBinder owner) { + mOwner = owner; } - if (mLocalInsetsSources == null) { - return; + + void addSourceId(int id) { + mSourceIds.add(id); } - final int id = provider.getId(); - if (mLocalInsetsSources.get(id) == null) { - if (DEBUG) { - Slog.d(TAG, "Given " + provider + " doesn't have a local insets source."); + void removeSourceId(int id) { + mSourceIds.remove(id); + } + + boolean hasSource() { + return !mSourceIds.isEmpty(); + } + + @Override + public void binderDied() { + synchronized (mWmService.mGlobalLock) { + boolean changed = false; + for (int i = mSourceIds.size() - 1; i >= 0; i--) { + changed |= removeLocalInsetsSource(mSourceIds.valueAt(i)); + } + mSourceIds.clear(); + mOwner.unlinkToDeath(this, 0); + mInsetsOwnerDeathRecipientMap.remove(mOwner); + if (changed && mDisplayContent != null) { + mDisplayContent.getInsetsStateController().updateAboveInsetsState(true); + } } - return; } - mLocalInsetsSources.remove(id); + } - // Update insets if this window is attached. - if (mDisplayContent != null) { + void removeLocalInsetsFrameProvider(InsetsFrameProvider provider, IBinder owner) { + if (provider == null || owner == null) { + throw new IllegalArgumentException("Insets provider or owner not specified."); + } + final int id = provider.getId(); + if (removeLocalInsetsSource(id) && mDisplayContent != null) { mDisplayContent.getInsetsStateController().updateAboveInsetsState(true); } + if (mInsetsOwnerDeathRecipientMap == null) { + return; + } + final DeathRecipient deathRecipient = mInsetsOwnerDeathRecipientMap.get(owner); + if (deathRecipient == null) { + return; + } + deathRecipient.removeSourceId(id); + if (!deathRecipient.hasSource()) { + owner.unlinkToDeath(deathRecipient, 0); + mInsetsOwnerDeathRecipientMap.remove(owner); + } + } + + private boolean removeLocalInsetsSource(int id) { + if (mLocalInsetsSources == null) { + return false; + } + if (mLocalInsetsSources.removeReturnOld(id) == null) { + if (DEBUG) { + Slog.d(TAG, "Given id " + Integer.toHexString(id) + " doesn't exist."); + } + return false; + } + return true; } /** diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 31918f478e44..d1b00a38701d 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -1090,7 +1090,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub + container); break; } - container.addLocalInsetsFrameProvider(hop.getInsetsFrameProvider()); + container.addLocalInsetsFrameProvider( + hop.getInsetsFrameProvider(), hop.getInsetsFrameOwner()); break; } case HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER: { @@ -1100,7 +1101,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub + container); break; } - container.removeLocalInsetsFrameProvider(hop.getInsetsFrameProvider()); + container.removeLocalInsetsFrameProvider( + hop.getInsetsFrameProvider(), hop.getInsetsFrameOwner()); break; } case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: { diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index e76cbe448f15..c065cb5f4ebe 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -971,7 +971,7 @@ void NativeInputManager::notifyDropWindow(const sp<IBinder>& token, float x, flo void NativeInputManager::notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp, const std::set<gui::Uid>& uids) { static const bool ENABLE_INPUT_DEVICE_USAGE_METRICS = - sysprop::InputProperties::enable_input_device_usage_metrics().value_or(false); + sysprop::InputProperties::enable_input_device_usage_metrics().value_or(true); if (!ENABLE_INPUT_DEVICE_USAGE_METRICS) return; mInputManager->getMetricsCollector().notifyDeviceInteraction(deviceId, timestamp, uids); diff --git a/services/core/xsd/display-layout-config/display-layout-config.xsd b/services/core/xsd/display-layout-config/display-layout-config.xsd index ce022e9cb78d..57b5d00f75a0 100644 --- a/services/core/xsd/display-layout-config/display-layout-config.xsd +++ b/services/core/xsd/display-layout-config/display-layout-config.xsd @@ -53,6 +53,7 @@ <xs:element name="position" type="xs:string" minOccurs="0" maxOccurs="1" /> <xs:element name="brightnessThrottlingMapId" type="xs:string" minOccurs="0" maxOccurs="1" /> <xs:element name="refreshRateThermalThrottlingMapId" type="xs:string" minOccurs="0" /> + <xs:element name="leadDisplayAddress" type="xs:nonNegativeInteger" minOccurs="0" maxOccurs="1" /> </xs:sequence> <xs:attribute name="enabled" type="xs:boolean" use="optional" /> <xs:attribute name="defaultDisplay" type="xs:boolean" use="optional" /> diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt index 42a800db1474..2d4f7a428db1 100644 --- a/services/core/xsd/display-layout-config/schema/current.txt +++ b/services/core/xsd/display-layout-config/schema/current.txt @@ -6,6 +6,7 @@ package com.android.server.display.config.layout { method public java.math.BigInteger getAddress(); method public String getBrightnessThrottlingMapId(); method public String getDisplayGroup(); + method public java.math.BigInteger getLeadDisplayAddress(); method public String getPosition(); method public String getRefreshRateThermalThrottlingMapId(); method public String getRefreshRateZoneId(); @@ -16,6 +17,7 @@ package com.android.server.display.config.layout { method public void setDefaultDisplay(boolean); method public void setDisplayGroup(String); method public void setEnabled(boolean); + method public void setLeadDisplayAddress(java.math.BigInteger); method public void setPosition(String); method public void setRefreshRateThermalThrottlingMapId(String); method public void setRefreshRateZoneId(String); diff --git a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java index 130e6ad91b49..4b124caca7d6 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java @@ -19,8 +19,8 @@ package com.android.server.display; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; -import android.view.Display; import android.view.DisplayAddress; import androidx.test.filters.SmallTest; @@ -65,9 +65,15 @@ public class DeviceStateToLayoutMapTest { Layout testLayout = new Layout(); createDefaultDisplay(testLayout, 123456L); - createNonDefaultDisplay(testLayout, 78910L, /* enabled= */ false, /* group= */ null); - createNonDefaultDisplay(testLayout, 98765L, /* enabled= */ true, /* group= */ "group1"); - createNonDefaultDisplay(testLayout, 786L, /* enabled= */ false, /* group= */ "group2"); + createNonDefaultDisplay(testLayout, 78910L, /* enabled= */ false, /* group= */ null, + /* leadDisplayAddress= */ null); + createNonDefaultDisplay(testLayout, 98765L, /* enabled= */ true, /* group= */ "group1", + /* leadDisplayAddress= */ null); + createNonDefaultDisplay(testLayout, 786L, /* enabled= */ false, /* group= */ "group2", + /* leadDisplayAddress= */ null); + createNonDefaultDisplay(testLayout, 1092L, /* enabled= */ true, /* group= */ null, + DisplayAddress.fromPhysicalDisplayId(78910L)); + testLayout.postProcessLocked(); assertEquals(testLayout, configLayout); } @@ -78,7 +84,9 @@ public class DeviceStateToLayoutMapTest { Layout testLayout = new Layout(); createDefaultDisplay(testLayout, 78910L); - createNonDefaultDisplay(testLayout, 123456L, /* enabled= */ false, /* group= */ null); + createNonDefaultDisplay(testLayout, 123456L, /* enabled= */ false, /* group= */ null, + /* leadDisplayAddress= */ null); + testLayout.postProcessLocked(); assertEquals(testLayout, configLayout); } @@ -122,20 +130,132 @@ public class DeviceStateToLayoutMapTest { Layout testLayout = new Layout(); testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(345L), /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null, - mDisplayIdProducerMock, Layout.Display.POSITION_FRONT, Display.DEFAULT_DISPLAY, - /* brightnessThrottlingMapId= */ "brightness1", + mDisplayIdProducerMock, Layout.Display.POSITION_FRONT, + /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness1", /* refreshRateZoneId= */ "zone1", /* refreshRateThermalThrottlingMapId= */ "rr1"); testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(678L), /* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ "group1", - mDisplayIdProducerMock, Layout.Display.POSITION_REAR, Display.DEFAULT_DISPLAY, - /* brightnessThrottlingMapId= */ "brightness2", + mDisplayIdProducerMock, Layout.Display.POSITION_REAR, + /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness2", /* refreshRateZoneId= */ "zone2", /* refreshRateThermalThrottlingMapId= */ "rr2"); + testLayout.postProcessLocked(); assertEquals(testLayout, configLayout); } + @Test + public void testLeadDisplayAddress() { + Layout layout = new Layout(); + createNonDefaultDisplay(layout, 111L, /* enabled= */ true, /* group= */ null, + /* leadDisplayAddress= */ null); + createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null, + DisplayAddress.fromPhysicalDisplayId(111L)); + + layout.postProcessLocked(); + + com.android.server.display.layout.Layout.Display display111 = + layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(111)); + com.android.server.display.layout.Layout.Display display222 = + layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(222)); + assertEquals(display111.getLeadDisplayId(), layout.NO_LEAD_DISPLAY); + assertEquals(display222.getLeadDisplayId(), display111.getLogicalDisplayId()); + } + + @Test + public void testLeadDisplayAddress_defaultDisplay() { + Layout layout = new Layout(); + createDefaultDisplay(layout, 123456L); + + layout.postProcessLocked(); + + com.android.server.display.layout.Layout.Display display = + layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(123456)); + assertEquals(display.getLeadDisplayId(), layout.NO_LEAD_DISPLAY); + } + + @Test + public void testLeadDisplayAddress_noLeadDisplay() { + Layout layout = new Layout(); + createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null, + /* leadDisplayAddress= */ null); + + layout.postProcessLocked(); + + com.android.server.display.layout.Layout.Display display = + layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(222)); + assertEquals(display.getLeadDisplayId(), layout.NO_LEAD_DISPLAY); + } + + @Test + public void testLeadDisplayAddress_selfLeadDisplayForNonDefaultDisplay() { + Layout layout = new Layout(); + + assertThrows("Expected Layout to throw IllegalArgumentException when the display points out" + + " itself as a lead display", + IllegalArgumentException.class, + () -> layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(123L), + /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null, + mDisplayIdProducerMock, Layout.Display.POSITION_FRONT, + DisplayAddress.fromPhysicalDisplayId(123L), + /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null, + /* refreshRateThermalThrottlingMapId= */ null)); + } + + @Test + public void testLeadDisplayAddress_wrongLeadDisplayForDefaultDisplay() { + Layout layout = new Layout(); + + assertThrows("Expected Layout to throw IllegalArgumentException when the default display " + + "has a lead display", + IllegalArgumentException.class, + () -> layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(123L), + /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null, + mDisplayIdProducerMock, Layout.Display.POSITION_FRONT, + DisplayAddress.fromPhysicalDisplayId(987L), + /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null, + /* refreshRateThermalThrottlingMapId= */ null)); + } + + @Test + public void testLeadDisplayAddress_notExistingLeadDisplayForNonDefaultDisplay() { + Layout layout = new Layout(); + createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null, + DisplayAddress.fromPhysicalDisplayId(111L)); + + assertThrows("Expected Layout to throw IllegalArgumentException when a lead display doesn't" + + " exist", IllegalArgumentException.class, () -> layout.postProcessLocked()); + } + + @Test + public void testLeadDisplayAddress_leadDisplayInDifferentDisplayGroup() { + Layout layout = new Layout(); + createNonDefaultDisplay(layout, 111, /* enabled= */ true, /* group= */ "group1", + /* leadDisplayAddress= */ null); + createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ "group2", + DisplayAddress.fromPhysicalDisplayId(111L)); + + assertThrows("Expected Layout to throw IllegalArgumentException when pointing to a lead " + + "display in the different group", + IllegalArgumentException.class, () -> layout.postProcessLocked()); + } + + @Test + public void testLeadDisplayAddress_cyclicLeadDisplay() { + Layout layout = new Layout(); + createNonDefaultDisplay(layout, 111, /* enabled= */ true, /* group= */ null, + DisplayAddress.fromPhysicalDisplayId(222L)); + createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null, + DisplayAddress.fromPhysicalDisplayId(333L)); + createNonDefaultDisplay(layout, 333L, /* enabled= */ true, /* group= */ null, + DisplayAddress.fromPhysicalDisplayId(222L)); + + assertThrows("Expected Layout to throw IllegalArgumentException when pointing to a lead " + + "display in the different group", + IllegalArgumentException.class, () -> layout.postProcessLocked()); + } + //////////////////// // Helper Methods // //////////////////// @@ -145,10 +265,11 @@ public class DeviceStateToLayoutMapTest { mDisplayIdProducerMock); } - private void createNonDefaultDisplay(Layout layout, long id, boolean enabled, String group) { + private void createNonDefaultDisplay(Layout layout, long id, boolean enabled, String group, + DisplayAddress leadDisplayAddress) { layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(id), /* isDefault= */ false, enabled, group, mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN, - Display.DEFAULT_DISPLAY, /* brightnessThrottlingMapId= */ null, + leadDisplayAddress, /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null); } @@ -177,6 +298,10 @@ public class DeviceStateToLayoutMapTest { + "<display enabled=\"false\" displayGroup=\"group2\">\n" + "<address>786</address>\n" + "</display>\n" + + "<display enabled=\"true\">\n" + + "<address>1092</address>\n" + + "<leadDisplayAddress>78910</leadDisplayAddress>\n" + + "</display>\n" + "</layout>\n" + "<layout>\n" diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java index 1eec70da3d20..3e695c9407b6 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -604,13 +604,13 @@ public class LogicalDisplayMapperTest { layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, /* isDefault= */ true, /* isEnabled= */ true, /* displayGroup= */ null, mIdProducer, POSITION_UNKNOWN, - /* leadDisplayId= */ Display.DEFAULT_DISPLAY, + /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "concurrent", /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null); layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, /* isDefault= */ false, /* isEnabled= */ true, /* displayGroup= */ null, mIdProducer, POSITION_UNKNOWN, - /* leadDisplayId= */ Display.DEFAULT_DISPLAY, + /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "concurrent", /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null); when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout); @@ -646,7 +646,7 @@ public class LogicalDisplayMapperTest { assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked()); assertEquals(-1, mLogicalDisplayMapper.getDisplayLocked(device1) .getLeadDisplayIdLocked()); - assertEquals(0, mLogicalDisplayMapper.getDisplayLocked(device2) + assertEquals(-1, mLogicalDisplayMapper.getDisplayLocked(device2) .getLeadDisplayIdLocked()); assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device1) .getDisplayInfoLocked().thermalBrightnessThrottlingDataId); @@ -811,7 +811,7 @@ public class LogicalDisplayMapperTest { mIdProducer); layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, /* isDefault= */ false, /* isEnabled= */ true, /* displayGroupName= */ null, - mIdProducer, POSITION_REAR, Display.DEFAULT_DISPLAY, + mIdProducer, POSITION_REAR, /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */null); when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout); @@ -861,7 +861,7 @@ public class LogicalDisplayMapperTest { private void createNonDefaultDisplay(Layout layout, DisplayAddress address, boolean enabled, String group) { layout.createDisplayLocked(address, /* isDefault= */ false, enabled, group, mIdProducer, - Layout.Display.POSITION_UNKNOWN, Display.DEFAULT_DISPLAY, + Layout.Display.POSITION_UNKNOWN, /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index e49b2907c860..30180e8f43a7 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -62,6 +62,8 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION_CODES.O_MR1; import static android.os.Build.VERSION_CODES.P; import static android.os.PowerManager.PARTIAL_WAKE_LOCK; +import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; import static android.os.UserHandle.USER_SYSTEM; import static android.os.UserManager.USER_TYPE_FULL_SECONDARY; import static android.os.UserManager.USER_TYPE_PROFILE_CLONE; @@ -83,6 +85,9 @@ import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.No import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; +import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; +import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER; +import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER; import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE; import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED; import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED; @@ -186,6 +191,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Looper; import android.os.Parcel; +import android.os.Parcelable; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.Process; @@ -716,6 +722,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @After public void assertAllWakeLocksReleased() { + waitForIdle(); // Finish async work. for (WakeLock wakeLock : mAcquiredWakeLocks) { assertThat(wakeLock.isHeld()).isFalse(); } @@ -5910,6 +5917,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testVisitUris_wearableExtender() { + Icon actionIcon = Icon.createWithContentUri("content://media/action"); + Icon wearActionIcon = Icon.createWithContentUri("content://media/wearAction"); + PendingIntent intent = PendingIntent.getActivity(mContext, 0, new Intent(), + PendingIntent.FLAG_IMMUTABLE); + Notification n = new Notification.Builder(mContext, "a") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .addAction(new Notification.Action.Builder(actionIcon, "Hey!", intent).build()) + .extend(new Notification.WearableExtender().addAction( + new Notification.Action.Builder(wearActionIcon, "Wear!", intent).build())) + .build(); + + Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); + n.visitUris(visitor); + + verify(visitor).accept(eq(actionIcon.getUri())); + verify(visitor).accept(eq(wearActionIcon.getUri())); + } + + @Test public void testSetNotificationPolicy_preP_setOldFields() { ZenModeHelper mZenModeHelper = mock(ZenModeHelper.class); mService.mZenModeHelper = mZenModeHelper; @@ -12097,6 +12124,70 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); // old } + @Test + public void enqueueNotification_allowlistsPendingIntents() throws RemoteException { + PendingIntent contentIntent = createPendingIntent("content"); + PendingIntent actionIntent1 = createPendingIntent("action1"); + PendingIntent actionIntent2 = createPendingIntent("action2"); + Notification n = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(contentIntent) + .addAction(new Notification.Action.Builder(null, "action1", actionIntent1).build()) + .addAction(new Notification.Action.Builder(null, "action2", actionIntent2).build()) + .build(); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 1, + parcelAndUnparcel(n, Notification.CREATOR), mUserId); + + verify(mAmi, times(3)).setPendingIntentAllowlistDuration( + any(), any(), anyLong(), + eq(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED), + eq(REASON_NOTIFICATION_SERVICE), any()); + verify(mAmi, times(3)).setPendingIntentAllowBgActivityStarts(any(), + any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER)); + } + + @Test + public void enqueueNotification_allowlistsPendingIntents_includingFromPublicVersion() + throws RemoteException { + PendingIntent contentIntent = createPendingIntent("content"); + PendingIntent actionIntent = createPendingIntent("action"); + PendingIntent publicContentIntent = createPendingIntent("publicContent"); + PendingIntent publicActionIntent = createPendingIntent("publicAction"); + Notification source = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(contentIntent) + .addAction(new Notification.Action.Builder(null, "action", actionIntent).build()) + .setPublicVersion(new Notification.Builder(mContext, "channel") + .setContentIntent(publicContentIntent) + .addAction(new Notification.Action.Builder( + null, "publicAction", publicActionIntent).build()) + .build()) + .build(); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 1, + parcelAndUnparcel(source, Notification.CREATOR), mUserId); + + verify(mAmi, times(4)).setPendingIntentAllowlistDuration( + any(), any(), anyLong(), + eq(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED), + eq(REASON_NOTIFICATION_SERVICE), any()); + verify(mAmi, times(4)).setPendingIntentAllowBgActivityStarts(any(), + any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER)); + } + + private static <T extends Parcelable> T parcelAndUnparcel(T source, + Parcelable.Creator<T> creator) { + Parcel parcel = Parcel.obtain(); + source.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + return creator.createFromParcel(parcel); + } + + private PendingIntent createPendingIntent(String action) { + return PendingIntent.getActivity(mContext, 0, + new Intent(action).setPackage(mContext.getPackageName()), + PendingIntent.FLAG_MUTABLE); + } + private void setDpmAppOppsExemptFromDismissal(boolean isOn) { DeviceConfig.setProperty( DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java index d32289d5ba6e..121e29609df5 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java @@ -67,6 +67,8 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -88,7 +90,6 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { // This list should be emptied! Items can be removed as bugs are fixed. private static final Multimap<Class<?>, String> KNOWN_BAD = ImmutableMultimap.<Class<?>, String>builder() - .put(Notification.WearableExtender.class, "addAction") // TODO: b/281044385 .put(Person.Builder.class, "setUri") // TODO: b/281044385 .put(RemoteViews.class, "setRemoteAdapter") // TODO: b/281044385 .build(); @@ -164,7 +165,7 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { /* extenderClass= */ Notification.WearableExtender.class, /* actionExtenderClass= */ Notification.Action.WearableExtender.class, /* includeRemoteViews= */ true); - assertThat(notification.includedUris.size()).isAtLeast(730); + assertThat(notification.includedUris.size()).isAtLeast(900); } @Test @@ -450,19 +451,43 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { Log.i(TAG, "About to generate parameters for " + ReflectionUtils.methodToString(executable) + " in " + where); - Class<?>[] parameterTypes = executable.getParameterTypes(); + Type[] parameterTypes = executable.getGenericParameterTypes(); Object[] parameterValues = new Object[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { - parameterValues[i] = generateObject( + parameterValues[i] = generateParameter( parameterTypes[i], where.plus(executable, - String.format("[%d,%s]", i, parameterTypes[i].getName())), + String.format("[%d,%s]", i, parameterTypes[i].getTypeName())), excludingClasses, specialGenerator); } return parameterValues; } + private static Object generateParameter(Type parameterType, Location where, + Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { + if (parameterType instanceof Class<?> parameterClass) { + return generateObject( + parameterClass, + where, + excludingClasses, + specialGenerator); + } else if (parameterType instanceof ParameterizedType parameterizedType) { + if (parameterizedType.getRawType().equals(List.class) + && parameterizedType.getActualTypeArguments()[0] instanceof Class<?>) { + ArrayList listValue = new ArrayList(); + for (int i = 0; i < NUM_ELEMENTS_IN_ARRAY; i++) { + listValue.add( + generateObject((Class<?>) parameterizedType.getActualTypeArguments()[0], + where, excludingClasses, specialGenerator)); + } + return listValue; + } + } + throw new IllegalArgumentException( + "I have no idea how to produce a(n) " + parameterType + ", sorry"); + } + private static class ReflectionUtils { static Set<Class<?>> getConcreteSubclasses(Class<?> clazz, Class<?> containerClass) { return Arrays.stream(containerClass.getDeclaredClasses()) diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java index 7cb7c79d63a0..2b19ad97e90c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java @@ -107,6 +107,9 @@ public class LaunchParamsPersisterTests extends WindowTestsBase { InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(); mFolder = new File(cacheFolder, "launch_params_tests"); deleteRecursively(mFolder); + mFolder.mkdir(); + mUserFolderGetter.apply(TEST_USER_ID).mkdir(); + mUserFolderGetter.apply(ALTERNATIVE_USER_ID).mkdir(); mDisplayUniqueId = "test:" + sNextUniqueId++; mTestDisplay = new TestDisplayContent.Builder(mAtm, 1000, 1500) diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index 2d8ddfa47300..a82459fc1baa 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -64,10 +64,19 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.clearInvocations; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Rect; +import android.os.Binder; +import android.os.DeadObjectException; +import android.os.IBinder; +import android.os.IInterface; +import android.os.Parcel; import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCallback; import android.platform.test.annotations.Presubmit; import android.view.IRemoteAnimationFinishedCallback; import android.view.IRemoteAnimationRunner; @@ -87,8 +96,10 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +import java.io.FileDescriptor; import java.util.ArrayList; import java.util.Comparator; +import java.util.NoSuchElementException; /** @@ -1404,7 +1415,7 @@ public class WindowContainerTests extends WindowTestsBase { } @Test - public void testAddLocalInsetsSourceProvider() { + public void testAddLocalInsetsFrameProvider() { /* ___ rootTask _______________________________________________ | | | | @@ -1435,19 +1446,20 @@ public class WindowContainerTests extends WindowTestsBase { TYPE_BASE_APPLICATION); attrs2.setTitle("AppWindow2"); activity2.addWindow(createWindowState(attrs2, activity2)); + final Binder owner = new Binder(); Rect genericOverlayInsetsRect1 = new Rect(0, 200, 1080, 700); Rect genericOverlayInsetsRect2 = new Rect(0, 0, 1080, 200); final InsetsFrameProvider provider1 = - new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays()) + new InsetsFrameProvider(owner, 1, WindowInsets.Type.systemOverlays()) .setArbitraryRectangle(genericOverlayInsetsRect1); final InsetsFrameProvider provider2 = - new InsetsFrameProvider(null, 2, WindowInsets.Type.systemOverlays()) + new InsetsFrameProvider(owner, 2, WindowInsets.Type.systemOverlays()) .setArbitraryRectangle(genericOverlayInsetsRect2); final int sourceId1 = provider1.getId(); final int sourceId2 = provider2.getId(); - rootTask.addLocalInsetsFrameProvider(provider1); - container.addLocalInsetsFrameProvider(provider2); + rootTask.addLocalInsetsFrameProvider(provider1, owner); + container.addLocalInsetsFrameProvider(provider2, owner); InsetsSource genericOverlayInsetsProvider1Source = new InsetsSource( sourceId1, systemOverlays()); @@ -1479,7 +1491,7 @@ public class WindowContainerTests extends WindowTestsBase { } @Test - public void testAddLocalInsetsSourceProvider_sameType_replacesInsets() { + public void testAddLocalInsetsFrameProvider_sameType_replacesInsets() { /* ___ rootTask ________________________________________ | | | @@ -1494,24 +1506,25 @@ public class WindowContainerTests extends WindowTestsBase { attrs.setTitle("AppWindow0"); activity0.addWindow(createWindowState(attrs, activity0)); + final Binder owner = new Binder(); final Rect genericOverlayInsetsRect1 = new Rect(0, 200, 1080, 700); final Rect genericOverlayInsetsRect2 = new Rect(0, 0, 1080, 200); final InsetsFrameProvider provider1 = - new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays()) + new InsetsFrameProvider(owner, 1, WindowInsets.Type.systemOverlays()) .setArbitraryRectangle(genericOverlayInsetsRect1); final InsetsFrameProvider provider2 = - new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays()) + new InsetsFrameProvider(owner, 1, WindowInsets.Type.systemOverlays()) .setArbitraryRectangle(genericOverlayInsetsRect2); final int sourceId1 = provider1.getId(); final int sourceId2 = provider2.getId(); - rootTask.addLocalInsetsFrameProvider(provider1); + rootTask.addLocalInsetsFrameProvider(provider1, owner); activity0.forAllWindows(window -> { assertEquals(genericOverlayInsetsRect1, window.getInsetsState().peekSource(sourceId1).getFrame()); }, true); - rootTask.addLocalInsetsFrameProvider(provider2); + rootTask.addLocalInsetsFrameProvider(provider2, owner); activity0.forAllWindows(window -> { assertEquals(genericOverlayInsetsRect2, @@ -1520,7 +1533,7 @@ public class WindowContainerTests extends WindowTestsBase { } @Test - public void testRemoveLocalInsetsSourceProvider() { + public void testRemoveLocalInsetsFrameProvider() { /* ___ rootTask _______________________________________________ | | | | @@ -1554,21 +1567,22 @@ public class WindowContainerTests extends WindowTestsBase { activity2.addWindow(createWindowState(attrs2, activity2)); + final Binder owner = new Binder(); final Rect navigationBarInsetsRect1 = new Rect(0, 200, 1080, 700); final Rect navigationBarInsetsRect2 = new Rect(0, 0, 1080, 200); final InsetsFrameProvider provider1 = - new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays()) + new InsetsFrameProvider(owner, 1, WindowInsets.Type.systemOverlays()) .setArbitraryRectangle(navigationBarInsetsRect1); final InsetsFrameProvider provider2 = - new InsetsFrameProvider(null, 2, WindowInsets.Type.systemOverlays()) + new InsetsFrameProvider(owner, 2, WindowInsets.Type.systemOverlays()) .setArbitraryRectangle(navigationBarInsetsRect2); final int sourceId1 = provider1.getId(); final int sourceId2 = provider2.getId(); - rootTask.addLocalInsetsFrameProvider(provider1); - container.addLocalInsetsFrameProvider(provider2); + rootTask.addLocalInsetsFrameProvider(provider1, owner); + container.addLocalInsetsFrameProvider(provider2, owner); mDisplayContent.getInsetsStateController().onPostLayout(); - rootTask.removeLocalInsetsFrameProvider(provider1); + rootTask.removeLocalInsetsFrameProvider(provider1, owner); mDisplayContent.getInsetsStateController().onPostLayout(); activity0.forAllWindows(window -> { @@ -1593,6 +1607,67 @@ public class WindowContainerTests extends WindowTestsBase { }, true); } + @Test + public void testAddLocalInsetsFrameProvider_ownerDiesAfterAdding() { + final Task task = createTask(mDisplayContent); + final TestBinder owner = new TestBinder(); + final InsetsFrameProvider provider = + new InsetsFrameProvider(owner, 0, WindowInsets.Type.systemOverlays()) + .setArbitraryRectangle(new Rect()); + task.addLocalInsetsFrameProvider(provider, owner); + + assertTrue("The death recipient must exist.", owner.hasDeathRecipient()); + assertTrue("The source must be added.", hasLocalSource(task, provider.getId())); + + // The owner dies after adding the source. + owner.die(); + + assertFalse("The death recipient must be removed.", owner.hasDeathRecipient()); + assertFalse("The source must be removed.", hasLocalSource(task, provider.getId())); + } + + @Test + public void testAddLocalInsetsFrameProvider_ownerDiesBeforeAdding() { + final Task task = createTask(mDisplayContent); + final TestBinder owner = new TestBinder(); + + // The owner dies before adding the source. + owner.die(); + + final InsetsFrameProvider provider = + new InsetsFrameProvider(owner, 0, WindowInsets.Type.systemOverlays()) + .setArbitraryRectangle(new Rect()); + task.addLocalInsetsFrameProvider(provider, owner); + + assertFalse("The death recipient must not exist.", owner.hasDeathRecipient()); + assertFalse("The source must not be added.", hasLocalSource(task, provider.getId())); + } + + @Test + public void testRemoveLocalInsetsFrameProvider_removeDeathRecipient() { + final Task task = createTask(mDisplayContent); + final TestBinder owner = new TestBinder(); + final InsetsFrameProvider provider = + new InsetsFrameProvider(owner, 0, WindowInsets.Type.systemOverlays()) + .setArbitraryRectangle(new Rect()); + task.addLocalInsetsFrameProvider(provider, owner); + + assertTrue("The death recipient must exist.", owner.hasDeathRecipient()); + assertTrue("The source must be added.", hasLocalSource(task, provider.getId())); + + task.removeLocalInsetsFrameProvider(provider, owner); + + assertFalse("The death recipient must be removed.", owner.hasDeathRecipient()); + assertFalse("The source must be removed.", hasLocalSource(task, provider.getId())); + } + + private static boolean hasLocalSource(WindowContainer container, int sourceId) { + if (container.mLocalInsetsSources == null) { + return false; + } + return container.mLocalInsetsSources.contains(sourceId); + } + /* Used so we can gain access to some protected members of the {@link WindowContainer} class */ private static class TestWindowContainer extends WindowContainer<TestWindowContainer> { private final int mLayer; @@ -1797,4 +1872,82 @@ public class WindowContainerTests extends WindowTestsBase { mIsVisibleRequested = isVisibleRequested; } } + + private static class TestBinder implements IBinder { + + private boolean mDead; + private final ArrayList<IBinder.DeathRecipient> mDeathRecipients = new ArrayList<>(); + + public void die() { + mDead = true; + for (int i = mDeathRecipients.size() - 1; i >= 0; i--) { + final DeathRecipient recipient = mDeathRecipients.get(i); + recipient.binderDied(this); + } + } + + public boolean hasDeathRecipient() { + return !mDeathRecipients.isEmpty(); + } + + @Override + public void linkToDeath(@NonNull DeathRecipient recipient, int flags) + throws RemoteException { + if (mDead) { + throw new DeadObjectException(); + } + mDeathRecipients.add(recipient); + } + + @Override + public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) { + final boolean successes = mDeathRecipients.remove(recipient); + if (successes || mDead) { + return successes; + } + throw new NoSuchElementException("Given recipient has not been registered."); + } + + @Override + public boolean isBinderAlive() { + return !mDead; + } + + @Override + public boolean pingBinder() { + return !mDead; + } + + @Nullable + @Override + public String getInterfaceDescriptor() throws RemoteException { + return null; + } + + @Nullable + @Override + public IInterface queryLocalInterface(@NonNull String descriptor) { + return null; + } + + @Override + public void dump(@NonNull FileDescriptor fd, @Nullable String[] args) + throws RemoteException { } + + @Override + public void dumpAsync(@NonNull FileDescriptor fd, @Nullable String[] args) + throws RemoteException { } + + @Override + public void shellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, + @Nullable FileDescriptor err, @NonNull String[] args, + @Nullable ShellCallback shellCallback, + @NonNull ResultReceiver resultReceiver) throws RemoteException { } + + @Override + public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) + throws RemoteException { + return false; + } + } } diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp index 37b529bb96c2..72b515947ca6 100644 --- a/tests/FlickerTests/Android.bp +++ b/tests/FlickerTests/Android.bp @@ -68,6 +68,13 @@ filegroup { srcs: ["src/**/rotation/*.kt"], } +filegroup { + name: "FlickerServiceTests-src", + srcs: [ + "src/com/android/server/wm/flicker/service/**/*.kt", + ], +} + java_defaults { name: "FlickerTestsDefault", manifest: "manifests/AndroidManifest.xml", @@ -110,6 +117,7 @@ android_test { ":FlickerTestsQuickswitch-src", ":FlickerTestsRotation-src", ":FlickerTestsNotification-src", + ":FlickerServiceTests-src", ], } @@ -194,6 +202,18 @@ android_test { ], } +android_test { + name: "FlickerServiceTests", + defaults: ["FlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestService.xml"], + package_name: "com.android.server.wm.flicker.service", + instrumentation_target_package: "com.android.server.wm.flicker.service", + srcs: [ + ":FlickerTestsBase-src", + ":FlickerServiceTests-src", + ], +} + java_library { name: "wm-flicker-common-assertions", platform_apis: true, diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml index ed63ec0a0e83..44a824513b91 100644 --- a/tests/FlickerTests/AndroidTestTemplate.xml +++ b/tests/FlickerTests/AndroidTestTemplate.xml @@ -87,6 +87,8 @@ value="/data/user/0/com.android.server.wm.flicker.rotation/files"/> <option name="directory-keys" value="/data/user/0/com.android.server.wm.flicker.notification/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.server.wm.flicker.service/files"/> <option name="collect-on-run-ended-only" value="true"/> <option name="clean-up" value="true"/> </metrics_collector> diff --git a/tests/FlickerTests/manifests/AndroidManifestService.xml b/tests/FlickerTests/manifests/AndroidManifestService.xml new file mode 100644 index 000000000000..3a7bc5095c08 --- /dev/null +++ b/tests/FlickerTests/manifests/AndroidManifestService.xml @@ -0,0 +1,24 @@ +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.wm.flicker.service"> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.wm.flicker.service" + android:label="WindowManager Flicker Service Tests"> + </instrumentation> +</manifest> diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/Utils.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/Utils.kt new file mode 100644 index 000000000000..8242e9a31992 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/Utils.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service + +import android.app.Instrumentation +import android.platform.test.rule.NavigationModeRule +import android.platform.test.rule.PressHomeRule +import android.platform.test.rule.UnlockScreenRule +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.apphelpers.MessagingAppHelper +import android.tools.device.flicker.rules.ChangeDisplayOrientationRule +import android.tools.device.flicker.rules.LaunchAppRule +import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.rules.RuleChain + +object Utils { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + + fun testSetupRule(navigationMode: NavBar, rotation: Rotation): RuleChain { + return RuleChain.outerRule(UnlockScreenRule()) + .around( + NavigationModeRule(navigationMode.value, /* changeNavigationModeAfterTest */ false) + ) + .around( + LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false) + ) + .around(RemoveAllTasksButHomeRule()) + .around( + ChangeDisplayOrientationRule( + rotation, + resetOrientationAfterTest = false, + clearCacheAfterParsing = false + ) + ) + .around(PressHomeRule()) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt new file mode 100644 index 000000000000..030b29269e48 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.close.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.close.scenarios.CloseAppBackButton +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class CloseAppBackButton3ButtonLandscape : + CloseAppBackButton(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) { + @ExpectedScenarios(["APP_CLOSE_TO_HOME"]) + @Test + override fun closeAppBackButtonTest() = super.closeAppBackButtonTest() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt new file mode 100644 index 000000000000..770da143b184 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.close.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.close.scenarios.CloseAppBackButton +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class CloseAppBackButton3ButtonPortrait : + CloseAppBackButton(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) { + @ExpectedScenarios(["APP_CLOSE_TO_HOME"]) + @Test + override fun closeAppBackButtonTest() = super.closeAppBackButtonTest() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt new file mode 100644 index 000000000000..4b2206d78276 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.close.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.close.scenarios.CloseAppBackButton +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class CloseAppBackButtonGesturalNavLandscape : + CloseAppBackButton(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) { + @ExpectedScenarios(["APP_CLOSE_TO_HOME"]) + @Test + override fun closeAppBackButtonTest() = super.closeAppBackButtonTest() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt new file mode 100644 index 000000000000..54b471e5ab28 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.close.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.close.scenarios.CloseAppBackButton +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class CloseAppBackButtonGesturalNavPortrait : + CloseAppBackButton(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) { + @ExpectedScenarios(["APP_CLOSE_TO_HOME"]) + @Test + override fun closeAppBackButtonTest() = super.closeAppBackButtonTest() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonLandscape.kt new file mode 100644 index 000000000000..47c25294f66e --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonLandscape.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.close.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.close.scenarios.CloseAppHomeButton +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class CloseAppHomeButton3ButtonLandscape : CloseAppHomeButton(Rotation.ROTATION_90) { + @ExpectedScenarios(["APP_CLOSE_TO_HOME"]) + @Test + override fun closeAppHomeButton() = super.closeAppHomeButton() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonPortrait.kt new file mode 100644 index 000000000000..b18148f97759 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonPortrait.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.close.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.close.scenarios.CloseAppHomeButton +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class CloseAppHomeButton3ButtonPortrait : CloseAppHomeButton(Rotation.ROTATION_0) { + @ExpectedScenarios(["APP_CLOSE_TO_HOME"]) + @Test + override fun closeAppHomeButton() = super.closeAppHomeButton() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavLandscape.kt new file mode 100644 index 000000000000..85430154e202 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavLandscape.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.close.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.close.scenarios.CloseAppSwipeToHome +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class CloseAppSwipeToHomeGesturalNavLandscape : CloseAppSwipeToHome(Rotation.ROTATION_90) { + @ExpectedScenarios(["APP_CLOSE_TO_HOME"]) + @Test + override fun closeAppSwipeToHome() = super.closeAppSwipeToHome() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavPortrait.kt new file mode 100644 index 000000000000..f88963b7a341 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavPortrait.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.close.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.close.scenarios.CloseAppSwipeToHome +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class CloseAppSwipeToHomeGesturalNavPortrait : CloseAppSwipeToHome(Rotation.ROTATION_0) { + @ExpectedScenarios(["APP_CLOSE_TO_HOME"]) + @Test + override fun closeAppSwipeToHome() = super.closeAppSwipeToHome() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppBackButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppBackButton.kt new file mode 100644 index 000000000000..2aaacde52547 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppBackButton.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.close.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.service.Utils +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class CloseAppBackButton( + val gestureMode: NavBar = NavBar.MODE_GESTURAL, + val rotation: Rotation = Rotation.ROTATION_0 +) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val testApp = SimpleAppHelper(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation) + + @Before + fun setup() { + tapl.setExpectedRotation(rotation.value) + testApp.launchViaIntent(wmHelper) + } + + @Test + open fun closeAppBackButtonTest() { + tapl.pressBack() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + } + + @After + fun teardown() { + testApp.exit(wmHelper) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppHomeButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppHomeButton.kt new file mode 100644 index 000000000000..08683a3ab43d --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppHomeButton.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.close.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.service.Utils +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class CloseAppHomeButton(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val testApp = SimpleAppHelper(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_3BUTTON, rotation) + + @Before + fun setup() { + tapl.setExpectedRotation(rotation.value) + testApp.launchViaIntent(wmHelper) + tapl.setExpectedRotationCheckEnabled(false) + } + + @Test + open fun closeAppHomeButton() { + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + } + + @After + fun teardown() { + testApp.exit(wmHelper) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppSwipeToHome.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppSwipeToHome.kt new file mode 100644 index 000000000000..360e11408c92 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppSwipeToHome.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.close.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.service.Utils +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class CloseAppSwipeToHome(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val testApp = SimpleAppHelper(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + tapl.setExpectedRotation(rotation.value) + testApp.launchViaIntent(wmHelper) + tapl.setExpectedRotationCheckEnabled(false) + } + + @Test + open fun closeAppSwipeToHome() { + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + } + + @After + fun teardown() { + testApp.exit(wmHelper) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavLandscape.kt new file mode 100644 index 000000000000..bb18770a8e7c --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavLandscape.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationCold +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromLockscreenNotificationCold3ButtonNavLandscape : + OpenAppFromLockscreenNotificationCold(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) { + @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) + @Test + override fun openAppFromLockscreenNotificationCold() = + super.openAppFromLockscreenNotificationCold() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavPortrait.kt new file mode 100644 index 000000000000..1c3cc2188c7c --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavPortrait.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationCold +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromLockscreenNotificationCold3ButtonNavPortrait : + OpenAppFromLockscreenNotificationCold(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) { + @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) + @Test + override fun openAppFromLockscreenNotificationCold() = + super.openAppFromLockscreenNotificationCold() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavLandscape.kt new file mode 100644 index 000000000000..46d36dbd6b09 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavLandscape.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationCold +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromLockscreenNotificationColdGesturalNavLandscape : + OpenAppFromLockscreenNotificationCold(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) { + @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) + @Test + override fun openAppFromLockscreenNotificationCold() = + super.openAppFromLockscreenNotificationCold() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavPortrait.kt new file mode 100644 index 000000000000..f6a668feeed6 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavPortrait.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationCold +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromLockscreenNotificationColdGesturalNavPortrait : + OpenAppFromLockscreenNotificationCold(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) { + @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) + @Test + override fun openAppFromLockscreenNotificationCold() = + super.openAppFromLockscreenNotificationCold() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavLandscape.kt new file mode 100644 index 000000000000..93200ba1b6fe --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavLandscape.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWarm +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromLockscreenNotificationWarm3ButtonNavLandscape : + OpenAppFromLockscreenNotificationWarm(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) { + @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) + @Test + override fun openAppFromLockscreenNotificationWarm() = + super.openAppFromLockscreenNotificationWarm() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavPortrait.kt new file mode 100644 index 000000000000..f5d41d250b1e --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavPortrait.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWarm +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromLockscreenNotificationWarm3ButtonNavPortrait : + OpenAppFromLockscreenNotificationWarm(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) { + @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) + @Test + override fun openAppFromLockscreenNotificationWarm() = + super.openAppFromLockscreenNotificationWarm() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavLandscape.kt new file mode 100644 index 000000000000..28f322bee130 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavLandscape.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWarm +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromLockscreenNotificationWarmGesturalNavLandscape : + OpenAppFromLockscreenNotificationWarm(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) { + @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) + @Test + override fun openAppFromLockscreenNotificationWarm() = + super.openAppFromLockscreenNotificationWarm() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavPortrait.kt new file mode 100644 index 000000000000..beb3fae8be48 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavPortrait.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWarm +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromLockscreenNotificationWarmGesturalNavPortrait : + OpenAppFromLockscreenNotificationWarm(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) { + @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) + @Test + override fun openAppFromLockscreenNotificationWarm() = + super.openAppFromLockscreenNotificationWarm() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt new file mode 100644 index 000000000000..b34da72ea5d6 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape : + OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) { + @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) + @Test + override fun openAppFromLockscreenNotificationWithOverlayApp() = + super.openAppFromLockscreenNotificationWithOverlayApp() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt new file mode 100644 index 000000000000..b1638979cd0d --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait : + OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) { + @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) + @Test + override fun openAppFromLockscreenNotificationWithOverlayApp() = + super.openAppFromLockscreenNotificationWithOverlayApp() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt new file mode 100644 index 000000000000..19b533ed57af --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape : + OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) { + @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) + @Test + override fun openAppFromLockscreenNotificationWithOverlayApp() = + super.openAppFromLockscreenNotificationWithOverlayApp() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt new file mode 100644 index 000000000000..c9ed4f42af06 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait : + OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) { + @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) + @Test + override fun openAppFromLockscreenNotificationWithOverlayApp() = + super.openAppFromLockscreenNotificationWithOverlayApp() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavLandscape.kt new file mode 100644 index 000000000000..866190f78827 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationCold +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromNotificationCold3ButtonNavLandscape : + OpenAppFromNotificationCold(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) { + @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) + @Test + override fun openAppFromNotificationCold() = super.openAppFromNotificationCold() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavPortrait.kt new file mode 100644 index 000000000000..a8d628328ed9 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationCold +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromNotificationCold3ButtonNavPortrait : + OpenAppFromNotificationCold(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) { + @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) + @Test + override fun openAppFromNotificationCold() = super.openAppFromNotificationCold() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavLandscape.kt new file mode 100644 index 000000000000..ef84c3f1177e --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationCold +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromNotificationColdGesturalNavLandscape : + OpenAppFromNotificationCold(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) { + @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) + @Test + override fun openAppFromNotificationCold() = super.openAppFromNotificationCold() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavPortrait.kt new file mode 100644 index 000000000000..5bb6b3713e9f --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationCold +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromNotificationColdGesturalNavPortrait : + OpenAppFromNotificationCold(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) { + @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) + @Test + override fun openAppFromNotificationCold() = super.openAppFromNotificationCold() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavLandscape.kt new file mode 100644 index 000000000000..39f25e97f04c --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationWarm +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromNotificationWarm3ButtonNavLandscape : + OpenAppFromNotificationWarm(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) { + @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) + @Test + override fun openAppFromNotificationWarm() = super.openAppFromNotificationWarm() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavPortrait.kt new file mode 100644 index 000000000000..28816bcba2a1 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationWarm +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromNotificationWarm3ButtonNavPortrait : + OpenAppFromNotificationWarm(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) { + @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) + @Test + override fun openAppFromNotificationWarm() = super.openAppFromNotificationWarm() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavLandscape.kt new file mode 100644 index 000000000000..94ffe4e57994 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavLandscape.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationWarm +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromNotificationWarmGesturalNavLandscape : + OpenAppFromNotificationWarm(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) { + @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) + @Test + override fun openAppFromNotificationWarm() = super.openAppFromNotificationWarm() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavPortrait.kt new file mode 100644 index 000000000000..e5f5ec4a6fe8 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavPortrait.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.flicker + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationWarm +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromNotificationWarmGesturalNavPortrait : + OpenAppFromNotificationWarm(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) { + @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"]) + @Test + override fun openAppFromNotificationWarm() = super.openAppFromNotificationWarm() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/NotificationUtils.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/NotificationUtils.kt new file mode 100644 index 000000000000..ac4e16969bad --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/NotificationUtils.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.scenarios + +import android.app.Instrumentation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.view.WindowInsets +import android.view.WindowManager +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until +import com.android.launcher3.tapl.LauncherInstrumentation + +object NotificationUtils { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + + fun openNotification(openingNotificationsFromLockScreen: Boolean) { + var startY = 10 + var endY = 3 * device.displayHeight / 4 + var steps = 25 + if (openingNotificationsFromLockScreen) { + val wm: WindowManager = + instrumentation.context.getSystemService(WindowManager::class.java) + ?: error("Unable to connect to WindowManager service") + val metricInsets = wm.currentWindowMetrics.windowInsets + val insets = + metricInsets.getInsetsIgnoringVisibility( + WindowInsets.Type.statusBars() or WindowInsets.Type.displayCutout() + ) + + startY = insets.top + 100 + endY = device.displayHeight / 2 + steps = 4 + } + + // Swipe down to show the notification shade + val x = device.displayWidth / 2 + device.swipe(x, startY, x, endY, steps) + device.waitForIdle(2000) + instrumentation.uiAutomation.syncInputTransactions() + + // Launch the activity by clicking the notification + val notification = + device.wait(Until.findObject(By.text("Flicker Test Notification")), 2000L) + notification?.click() ?: error("Notification not found") + instrumentation.uiAutomation.syncInputTransactions() + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationCold.kt new file mode 100644 index 000000000000..9c53ab30e8cd --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationCold.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.helpers.wakeUpAndGoToHomeScreen +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.NotificationAppHelper +import com.android.server.wm.flicker.service.Utils +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +abstract class OpenAppFromLockscreenNotificationCold( + val gestureMode: NavBar = NavBar.MODE_GESTURAL, + val rotation: Rotation = Rotation.ROTATION_0 +) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation) + + val openingNotificationsFromLockScreen = true + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation) + + @Before + fun setup() { + device.wakeUpAndGoToHomeScreen() + testApp.launchViaIntent(wmHelper) + wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify() + testApp.postNotification(wmHelper) + device.pressHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + + // Close the app that posted the notification to trigger a cold start next time + // it is open - can't just kill it because that would remove the notification. + tapl.setExpectedRotationCheckEnabled(false) + tapl.goHome() + tapl.workspace.switchToOverview() + tapl.overview.dismissAllTasks() + + device.sleep() + wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify() + } + + @Test + open fun openAppFromLockscreenNotificationCold() { + device.wakeUp() + + NotificationUtils.openNotification(openingNotificationsFromLockScreen) + // Wait for the app to launch + wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify() + } + + @After + fun teardown() { + testApp.exit(wmHelper) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWarm.kt new file mode 100644 index 000000000000..31f55d9d2e99 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWarm.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.helpers.wakeUpAndGoToHomeScreen +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.NotificationAppHelper +import com.android.server.wm.flicker.service.Utils +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +abstract class OpenAppFromLockscreenNotificationWarm( + val gestureMode: NavBar = NavBar.MODE_GESTURAL, + val rotation: Rotation = Rotation.ROTATION_0 +) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation) + + private val openingNotificationsFromLockScreen = true + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation) + + @Before + fun setup() { + device.wakeUpAndGoToHomeScreen() + testApp.launchViaIntent(wmHelper) + wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify() + testApp.postNotification(wmHelper) + device.pressHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + + device.sleep() + wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify() + } + + @Test + open fun openAppFromLockscreenNotificationWarm() { + device.wakeUp() + + NotificationUtils.openNotification(openingNotificationsFromLockScreen) + // Wait for the app to launch + wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify() + } + + @After + fun teardown() { + testApp.exit(wmHelper) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWithOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWithOverlayApp.kt new file mode 100644 index 000000000000..b971555f2747 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWithOverlayApp.kt @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.helpers.wakeUpAndGoToHomeScreen +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.NotificationAppHelper +import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper +import com.android.server.wm.flicker.service.Utils +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +abstract class OpenAppFromLockscreenNotificationWithOverlayApp( + val gestureMode: NavBar = NavBar.MODE_GESTURAL, + val rotation: Rotation = Rotation.ROTATION_0 +) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val showWhenLockedApp = ShowWhenLockedAppHelper(instrumentation) + private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation) + + // Although we are technically still locked here, the overlay app means we should open the + // notification shade as if we were unlocked. + private val openingNotificationsFromLockScreen = false + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation) + + @Before + fun setup() { + device.wakeUpAndGoToHomeScreen() + testApp.launchViaIntent(wmHelper) + wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify() + testApp.postNotification(wmHelper) + device.pressHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + + // Close the app that posted the notification to trigger a cold start next time + // it is open - can't just kill it because that would remove the notification. + tapl.setExpectedRotationCheckEnabled(false) + tapl.goHome() + tapl.workspace.switchToOverview() + tapl.overview.dismissAllTasks() + + device.sleep() + wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify() + + device.wakeUpAndGoToHomeScreen() + + // Launch an activity that is shown when the device is locked + showWhenLockedApp.launchViaIntent(wmHelper) + wmHelper.StateSyncBuilder().withFullScreenApp(showWhenLockedApp).waitForAndVerify() + + device.sleep() + wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify() + } + + @Test + open fun openAppFromLockscreenNotificationWithOverlayApp() { + device.wakeUp() + NotificationUtils.openNotification(openingNotificationsFromLockScreen) + // Wait for the app to launch + wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify() + } + + @After + fun teardown() { + testApp.exit(wmHelper) + showWhenLockedApp.exit(wmHelper) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationCold.kt new file mode 100644 index 000000000000..fed077ed0e50 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationCold.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.helpers.wakeUpAndGoToHomeScreen +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.NotificationAppHelper +import com.android.server.wm.flicker.service.Utils +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +abstract class OpenAppFromNotificationCold( + val gestureMode: NavBar = NavBar.MODE_GESTURAL, + val rotation: Rotation = Rotation.ROTATION_0 +) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val device = UiDevice.getInstance(instrumentation) + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation) + + private val openingNotificationsFromLockScreen = false + + @Before + fun setup() { + device.wakeUpAndGoToHomeScreen() + testApp.launchViaIntent(wmHelper) + wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify() + testApp.postNotification(wmHelper) + device.pressHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + + // Close the app that posted the notification to trigger a cold start next time + // it is open - can't just kill it because that would remove the notification. + tapl.setExpectedRotationCheckEnabled(false) + tapl.goHome() + tapl.workspace.switchToOverview() + tapl.overview.dismissAllTasks() + } + + @Test + open fun openAppFromNotificationCold() { + NotificationUtils.openNotification(openingNotificationsFromLockScreen) + // Wait for the app to launch + wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify() + } + + @After + fun teardown() { + testApp.exit(wmHelper) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationWarm.kt new file mode 100644 index 000000000000..403790e904d8 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationWarm.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.notification.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.helpers.wakeUpAndGoToHomeScreen +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.NotificationAppHelper +import com.android.server.wm.flicker.service.Utils +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +abstract class OpenAppFromNotificationWarm( + val gestureMode: NavBar = NavBar.MODE_GESTURAL, + val rotation: Rotation = Rotation.ROTATION_0 +) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val device = UiDevice.getInstance(instrumentation) + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation) + + private val openingNotificationsFromLockScreen = false + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation) + + @Before + fun setup() { + device.wakeUpAndGoToHomeScreen() + testApp.launchViaIntent(wmHelper) + wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify() + testApp.postNotification(wmHelper) + device.pressHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + } + + @Test + open fun openAppFromNotificationWarm() { + NotificationUtils.openNotification(openingNotificationsFromLockScreen) + // Wait for the app to launch + wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify() + } + + @After + fun teardown() { + testApp.exit(wmHelper) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavLandscape.kt new file mode 100644 index 000000000000..d611a420edcb --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavLandscape.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.server.wm.flicker.service.quickswitch.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchBetweenTwoAppsBack +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class QuickSwitchBetweenTwoAppsBackGesturalNavLandscape : + QuickSwitchBetweenTwoAppsBack(Rotation.ROTATION_90) { + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun quickSwitchBetweenTwoAppsBack() = super.quickSwitchBetweenTwoAppsBack() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavPortrait.kt new file mode 100644 index 000000000000..e6bcbba661f3 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavPortrait.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.server.wm.flicker.service.quickswitch.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchBetweenTwoAppsBack +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class QuickSwitchBetweenTwoAppsBackGesturalNavPortrait : + QuickSwitchBetweenTwoAppsBack(Rotation.ROTATION_0) { + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun quickSwitchBetweenTwoAppsBack() = super.quickSwitchBetweenTwoAppsBack() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavLandscape.kt new file mode 100644 index 000000000000..2a26d633adaf --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavLandscape.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.server.wm.flicker.service.quickswitch.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchBetweenTwoAppsForward +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class QuickSwitchBetweenTwoAppsForwardGesturalNavLandscape : + QuickSwitchBetweenTwoAppsForward(Rotation.ROTATION_90) { + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun quickSwitchBetweenTwoAppsForward() = super.quickSwitchBetweenTwoAppsForward() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavPortrait.kt new file mode 100644 index 000000000000..5ce714371db0 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavPortrait.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.server.wm.flicker.service.quickswitch.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchBetweenTwoAppsForward +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class QuickSwitchBetweenTwoAppsForwardGesturalNavPortrait : + QuickSwitchBetweenTwoAppsForward(Rotation.ROTATION_0) { + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun quickSwitchBetweenTwoAppsForward() = super.quickSwitchBetweenTwoAppsForward() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavLandscape.kt new file mode 100644 index 000000000000..cff47bb8cc19 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavLandscape.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.quickswitch.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchFromLauncher +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class QuickSwitchFromLauncherGesturalNavLandscape : QuickSwitchFromLauncher(Rotation.ROTATION_90) { + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun quickSwitchFromLauncher() = super.quickSwitchFromLauncher() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavPortrait.kt new file mode 100644 index 000000000000..33095a6ac078 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavPortrait.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.quickswitch.flicker + +import android.tools.common.Rotation +import android.tools.common.flicker.FlickerConfig +import android.tools.common.flicker.annotation.ExpectedScenarios +import android.tools.common.flicker.annotation.FlickerConfigProvider +import android.tools.common.flicker.config.FlickerConfig +import android.tools.common.flicker.config.FlickerServiceConfig +import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchFromLauncher +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class QuickSwitchFromLauncherGesturalNavPortrait : QuickSwitchFromLauncher(Rotation.ROTATION_0) { + @ExpectedScenarios(["QUICKSWITCH"]) + @Test + override fun quickSwitchFromLauncher() = super.quickSwitchFromLauncher() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsBack.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsBack.kt new file mode 100644 index 000000000000..93130c4680ff --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsBack.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.quickswitch.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.NonResizeableAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.service.Utils +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class QuickSwitchBetweenTwoAppsBack(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val testApp1 = SimpleAppHelper(instrumentation) + private val testApp2 = NonResizeableAppHelper(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + tapl.setExpectedRotation(rotation.value) + tapl.setIgnoreTaskbarVisibility(true) + testApp1.launchViaIntent(wmHelper) + testApp2.launchViaIntent(wmHelper) + } + + @Test + open fun quickSwitchBetweenTwoAppsBack() { + tapl.launchedAppState.quickSwitchToPreviousApp() + wmHelper + .StateSyncBuilder() + .withFullScreenApp(testApp1) + .withNavOrTaskBarVisible() + .withStatusBarVisible() + .waitForAndVerify() + } + + @After + fun teardown() { + testApp1.exit(wmHelper) + testApp2.exit(wmHelper) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt new file mode 100644 index 000000000000..4941eea12129 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.quickswitch.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.NonResizeableAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.service.Utils +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class QuickSwitchBetweenTwoAppsForward(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val testApp1 = SimpleAppHelper(instrumentation) + private val testApp2 = NonResizeableAppHelper(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + tapl.setExpectedRotation(rotation.value) + + testApp1.launchViaIntent(wmHelper) + testApp2.launchViaIntent(wmHelper) + tapl.launchedAppState.quickSwitchToPreviousApp() + wmHelper + .StateSyncBuilder() + .withFullScreenApp(testApp1) + .withNavOrTaskBarVisible() + .withStatusBarVisible() + .waitForAndVerify() + } + + @Test + open fun quickSwitchBetweenTwoAppsForward() { + tapl.launchedAppState.quickSwitchToPreviousAppSwipeLeft() + wmHelper + .StateSyncBuilder() + .withFullScreenApp(testApp2) + .withNavOrTaskBarVisible() + .withStatusBarVisible() + .waitForAndVerify() + } + + @After + fun teardown() { + testApp1.exit(wmHelper) + testApp2.exit(wmHelper) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchFromLauncher.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchFromLauncher.kt new file mode 100644 index 000000000000..7afe5268ede4 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchFromLauncher.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.service.quickswitch.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.service.Utils +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class QuickSwitchFromLauncher(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val testApp = SimpleAppHelper(instrumentation) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + tapl.setExpectedRotationCheckEnabled(false) + + tapl.setExpectedRotation(rotation.value) + + testApp.launchViaIntent(wmHelper) + tapl.goHome() + wmHelper + .StateSyncBuilder() + .withHomeActivityVisible() + .withWindowSurfaceDisappeared(testApp) + .waitForAndVerify() + } + + @Test + open fun quickSwitchFromLauncher() { + tapl.workspace.quickSwitchToPreviousApp() + wmHelper + .StateSyncBuilder() + .withFullScreenApp(testApp) + .withNavOrTaskBarVisible() + .withStatusBarVisible() + .waitForAndVerify() + } + + @After + fun teardown() { + testApp.exit(wmHelper) + } +} diff --git a/tests/UiBench/Android.bp b/tests/UiBench/Android.bp index 90e61c52da68..0d2f2ef46cab 100644 --- a/tests/UiBench/Android.bp +++ b/tests/UiBench/Android.bp @@ -24,6 +24,5 @@ android_test { "androidx.recyclerview_recyclerview", "androidx.leanback_leanback", ], - certificate: "platform", test_suites: ["device-tests"], } diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml index 47211c5fbad1..4fc6ec71f29c 100644 --- a/tests/UiBench/AndroidManifest.xml +++ b/tests/UiBench/AndroidManifest.xml @@ -18,7 +18,6 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.android.test.uibench"> - <uses-permission android:name="android.permission.INJECT_EVENTS" /> <application android:allowBackup="false" android:theme="@style/Theme.AppCompat.Light.DarkActionBar" diff --git a/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java index 1b2c3c60ffd4..06b65a7f9bbf 100644 --- a/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java +++ b/tests/UiBench/src/com/android/test/uibench/EditTextTypeActivity.java @@ -15,15 +15,11 @@ */ package com.android.test.uibench; -import android.app.Instrumentation; +import android.content.Intent; import android.os.Bundle; -import android.os.Looper; -import android.os.MessageQueue; -import androidx.appcompat.app.AppCompatActivity; -import android.view.KeyEvent; import android.widget.EditText; -import java.util.concurrent.Semaphore; +import androidx.appcompat.app.AppCompatActivity; /** * Note: currently incomplete, complexity of input continuously grows, instead of looping @@ -32,7 +28,13 @@ import java.util.concurrent.Semaphore; * Simulates typing continuously into an EditText. */ public class EditTextTypeActivity extends AppCompatActivity { - Thread mThread; + + /** + * Broadcast action: Used to notify UiBenchEditTextTypingMicrobenchmark test when the + * test activity was paused. + */ + private static final String ACTION_CANCEL_TYPING_CALLBACK = + "com.android.uibench.action.CANCEL_TYPING_CALLBACK"; private static String sSeedText = ""; static { @@ -46,9 +48,6 @@ public class EditTextTypeActivity extends AppCompatActivity { sSeedText = builder.toString(); } - final Object mLock = new Object(); - boolean mShouldStop = false; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -56,55 +55,13 @@ public class EditTextTypeActivity extends AppCompatActivity { EditText editText = new EditText(this); editText.setText(sSeedText); setContentView(editText); - - final Instrumentation instrumentation = new Instrumentation(); - final Semaphore sem = new Semaphore(0); - MessageQueue.IdleHandler handler = new MessageQueue.IdleHandler() { - @Override - public boolean queueIdle() { - // TODO: consider other signaling approaches - sem.release(); - return true; - } - }; - Looper.myQueue().addIdleHandler(handler); - synchronized (mLock) { - mShouldStop = false; - } - mThread = new Thread(new Runnable() { - int codes[] = { KeyEvent.KEYCODE_H, KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_L, - KeyEvent.KEYCODE_L, KeyEvent.KEYCODE_O, KeyEvent.KEYCODE_SPACE }; - int i = 0; - @Override - public void run() { - while (true) { - try { - sem.acquire(); - } catch (InterruptedException e) { - // TODO, maybe - } - int code = codes[i % codes.length]; - if (i % 100 == 99) code = KeyEvent.KEYCODE_ENTER; - - synchronized (mLock) { - if (mShouldStop) break; - } - - // TODO: bit of a race here, since the event can arrive after pause/stop. - // (Can't synchronize on key send, since it's synchronous.) - instrumentation.sendKeyDownUpSync(code); - i++; - } - } - }); - mThread.start(); } @Override protected void onPause() { - synchronized (mLock) { - mShouldStop = true; - } + // Cancel the typing when the test activity was paused. + sendBroadcast(new Intent(ACTION_CANCEL_TYPING_CALLBACK).addFlags( + Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY)); super.onPause(); } } |