Merge "[pm/incremental] disable unstartable state and hide related APIs"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index 394fba6..c4ce2cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -42,6 +42,7 @@
private ComponentName mLastPipComponentName;
private final DisplayInfo mDisplayInfo = new DisplayInfo();
private final DisplayLayout mDisplayLayout = new DisplayLayout();
+ private final @NonNull AnimatingBoundsState mAnimatingBoundsState = new AnimatingBoundsState();
/**
* Set the current PIP bounds.
@@ -151,6 +152,60 @@
mPipReentryState = null;
}
+ public AnimatingBoundsState getAnimatingBoundsState() {
+ return mAnimatingBoundsState;
+ }
+
+ /** Source of truth for the current animation bounds of PIP. */
+ public static class AnimatingBoundsState {
+ /** The bounds used when PIP is being dragged or animated. */
+ private final Rect mTemporaryBounds = new Rect();
+ /** The destination bounds to which PIP is animating. */
+ private final Rect mAnimatingToBounds = new Rect();
+
+ /** Whether PIP is being dragged or animated (e.g. resizing, in fling, etc). */
+ public boolean isAnimating() {
+ return !mTemporaryBounds.isEmpty();
+ }
+
+ /** Set the temporary bounds used to represent the drag or animation bounds of PIP. */
+ public void setTemporaryBounds(Rect bounds) {
+ mTemporaryBounds.set(bounds);
+ }
+
+ /** Set the bounds to which PIP is animating. */
+ public void setAnimatingToBounds(Rect bounds) {
+ mAnimatingToBounds.set(bounds);
+ }
+
+ /** Called when all ongoing dragging and animation operations have ended. */
+ public void onAllAnimationsEnded() {
+ mTemporaryBounds.setEmpty();
+ }
+
+ /** Called when an ongoing physics animation has ended. */
+ public void onPhysicsAnimationEnded() {
+ mAnimatingToBounds.setEmpty();
+ }
+
+ /** Returns the temporary animation bounds. */
+ public Rect getTemporaryBounds() {
+ return mTemporaryBounds;
+ }
+
+ /** Returns the destination bounds to which PIP is currently animating. */
+ public Rect getAnimatingToBounds() {
+ return mAnimatingToBounds;
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + AnimatingBoundsState.class.getSimpleName());
+ pw.println(innerPrefix + "mTemporaryBounds=" + mTemporaryBounds);
+ pw.println(innerPrefix + "mAnimatingToBounds=" + mAnimatingToBounds);
+ }
+ }
+
static final class PipReentryState {
private static final String TAG = PipReentryState.class.getSimpleName();
@@ -196,5 +251,6 @@
} else {
mPipReentryState.dump(pw, innerPrefix);
}
+ mAnimatingBoundsState.dump(pw, innerPrefix);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 9247c68..83cd63c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -42,7 +42,6 @@
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
-import java.io.PrintWriter;
import java.util.function.Consumer;
import kotlin.Unit;
@@ -80,20 +79,6 @@
/** The region that all of PIP must stay within. */
private final Rect mFloatingAllowedArea = new Rect();
- /**
- * Temporary bounds used when PIP is being dragged or animated. These bounds are applied to PIP
- * using {@link PipTaskOrganizer#scheduleUserResizePip}, so that we can animate shrinking into
- * and expanding out of the magnetic dismiss target.
- *
- * Once PIP is done being dragged or animated, we set {@link #mBounds} equal to these temporary
- * bounds, and call {@link PipTaskOrganizer#scheduleFinishResizePip} to 'officially' move PIP to
- * its new bounds.
- */
- private final Rect mTemporaryBounds = new Rect();
-
- /** The destination bounds to which PIP is animating. */
- private final Rect mAnimatingToBounds = new Rect();
-
private int mStashOffset = 0;
/** Coordinator instance for resolving conflicts with other floating content. */
@@ -108,15 +93,15 @@
});
/**
- * PhysicsAnimator instance for animating {@link #mTemporaryBounds} using physics animations.
+ * PhysicsAnimator instance for animating {@link PipBoundsState#getAnimatingBoundsState()}
+ * using physics animations.
*/
- private PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
- mTemporaryBounds);
+ private final PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator;
private MagnetizedObject<Rect> mMagnetizedPip;
/**
- * Update listener that resizes the PIP to {@link #mTemporaryBounds}.
+ * Update listener that resizes the PIP to {@link PipBoundsState#getAnimatingBoundsState()}.
*/
private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener;
@@ -189,14 +174,17 @@
mSnapAlgorithm = snapAlgorithm;
mFloatingContentCoordinator = floatingContentCoordinator;
mPipTaskOrganizer.registerPipTransitionCallback(mPipTransitionCallback);
+ mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
+ mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds());
mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler(
mSfAnimationHandlerThreadLocal.get());
+
reloadResources();
mResizePipUpdateListener = (target, values) -> {
- if (!mTemporaryBounds.isEmpty()) {
- mPipTaskOrganizer.scheduleUserResizePip(
- getBounds(), mTemporaryBounds, null);
+ if (mPipBoundsState.getAnimatingBoundsState().isAnimating()) {
+ mPipTaskOrganizer.scheduleUserResizePip(getBounds(),
+ mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds(), null);
}
};
}
@@ -209,7 +197,8 @@
@NonNull
@Override
public Rect getFloatingBoundsOnScreen() {
- return !mAnimatingToBounds.isEmpty() ? mAnimatingToBounds : getBounds();
+ return !mPipBoundsState.getAnimatingBoundsState().getAnimatingToBounds().isEmpty()
+ ? mPipBoundsState.getAnimatingBoundsState().getAnimatingToBounds() : getBounds();
}
@NonNull
@@ -227,18 +216,14 @@
* Synchronizes the current bounds with the pinned stack, cancelling any ongoing animations.
*/
void synchronizePinnedStackBounds() {
- cancelAnimations();
- mTemporaryBounds.setEmpty();
+ cancelPhysicsAnimation();
+ mPipBoundsState.getAnimatingBoundsState().onAllAnimationsEnded();
if (mPipTaskOrganizer.isInPip()) {
mFloatingContentCoordinator.onContentMoved(this);
}
}
- boolean isAnimating() {
- return mTemporaryBoundsPhysicsAnimator.isRunning();
- }
-
/**
* Tries to move the pinned stack to the given {@param bounds}.
*/
@@ -261,14 +246,14 @@
if (!mSpringingToTouch) {
// If we are moving PIP directly to the touch event locations, cancel any animations and
// move PIP to the given bounds.
- cancelAnimations();
+ cancelPhysicsAnimation();
if (!isDragging) {
resizePipUnchecked(toBounds);
mPipBoundsState.setBounds(toBounds);
} else {
- mTemporaryBounds.set(toBounds);
- mPipTaskOrganizer.scheduleUserResizePip(getBounds(), mTemporaryBounds,
+ mPipBoundsState.getAnimatingBoundsState().setTemporaryBounds(toBounds);
+ mPipTaskOrganizer.scheduleUserResizePip(getBounds(), toBounds,
(Rect newBounds) -> {
mMainHandler.post(() -> {
mMenuController.updateMenuLayout(newBounds);
@@ -303,9 +288,9 @@
final float destinationY = targetCenter.y - (desiredHeight / 2f);
// If we're already in the dismiss target area, then there won't be a move to set the
- // temporary bounds, so just initialize it to the current bounds
- if (mTemporaryBounds.isEmpty()) {
- mTemporaryBounds.set(getBounds());
+ // temporary bounds, so just initialize it to the current bounds.
+ if (!mPipBoundsState.getAnimatingBoundsState().isAnimating()) {
+ mPipBoundsState.getAnimatingBoundsState().setTemporaryBounds(getBounds());
}
mTemporaryBoundsPhysicsAnimator
.spring(FloatProperties.RECT_X, destinationX, velX, mSpringConfig)
@@ -339,7 +324,7 @@
Log.d(TAG, "exitPip: skipAnimation=" + skipAnimation
+ " callers=\n" + Debug.getCallers(5, " "));
}
- cancelAnimations();
+ cancelPhysicsAnimation();
mMenuController.hideMenuWithoutResize();
mPipTaskOrganizer.getUpdateHandler().post(() -> {
mPipTaskOrganizer.exitPip(skipAnimation
@@ -356,7 +341,7 @@
if (DEBUG) {
Log.d(TAG, "removePip: callers=\n" + Debug.getCallers(5, " "));
}
- cancelAnimations();
+ cancelPhysicsAnimation();
mMenuController.hideMenuWithoutResize();
mPipTaskOrganizer.removePip();
}
@@ -383,14 +368,6 @@
}
/**
- * Returns the PIP bounds if we're not animating, or the current, temporary animating bounds
- * otherwise.
- */
- Rect getPossiblyAnimatingBounds() {
- return mTemporaryBounds.isEmpty() ? getBounds() : mTemporaryBounds;
- }
-
- /**
* Flings the PiP to the closest snap target.
*/
void flingToSnapTarget(
@@ -428,9 +405,10 @@
: mMovementBounds.right;
final float xEndValue = velocityX < 0 ? leftEdge : rightEdge;
+
+ final int startValueY = mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds().top;
final float estimatedFlingYEndValue =
- PhysicsAnimator.estimateFlingEndValue(
- mTemporaryBounds.top, velocityY, mFlingConfigY);
+ PhysicsAnimator.estimateFlingEndValue(startValueY, velocityY, mFlingConfigY);
startBoundsAnimator(xEndValue /* toX */, estimatedFlingYEndValue /* toY */,
false /* dismiss */);
@@ -443,7 +421,7 @@
void animateToBounds(Rect bounds, PhysicsAnimator.SpringConfig springConfig) {
if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
// Animate from the current bounds if we're not already animating.
- mTemporaryBounds.set(getBounds());
+ mPipBoundsState.getAnimatingBoundsState().setTemporaryBounds(getBounds());
}
mTemporaryBoundsPhysicsAnimator
@@ -513,7 +491,7 @@
Log.d(TAG, "animateToOffset: originalBounds=" + originalBounds + " offset=" + offset
+ " callers=\n" + Debug.getCallers(5, " "));
}
- cancelAnimations();
+ cancelPhysicsAnimation();
mPipTaskOrganizer.scheduleOffsetPip(originalBounds, offset, SHIFT_DURATION,
mUpdateBoundsCallback);
}
@@ -521,9 +499,9 @@
/**
* Cancels all existing animations.
*/
- private void cancelAnimations() {
+ private void cancelPhysicsAnimation() {
mTemporaryBoundsPhysicsAnimator.cancel();
- mAnimatingToBounds.setEmpty();
+ mPipBoundsState.getAnimatingBoundsState().onPhysicsAnimationEnded();
mSpringingToTouch = false;
}
@@ -547,22 +525,19 @@
*/
private void startBoundsAnimator(float toX, float toY, boolean dismiss) {
if (!mSpringingToTouch) {
- cancelAnimations();
+ cancelPhysicsAnimation();
}
- // Set animatingToBounds directly to avoid allocating a new Rect, but then call
- // setAnimatingToBounds to run the normal logic for changing animatingToBounds.
- mAnimatingToBounds.set(
+ setAnimatingToBounds(new Rect(
(int) toX,
(int) toY,
(int) toX + getBounds().width(),
- (int) toY + getBounds().height());
- setAnimatingToBounds(mAnimatingToBounds);
+ (int) toY + getBounds().height()));
if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
mTemporaryBoundsPhysicsAnimator
.addUpdateListener(mResizePipUpdateListener)
- .withEndActions(this::onBoundsAnimationEnd);
+ .withEndActions(this::onBoundsPhysicsAnimationEnd);
}
mTemporaryBoundsPhysicsAnimator.start();
@@ -576,31 +551,34 @@
mDismissalPending = true;
}
- private void onBoundsAnimationEnd() {
+ private void onBoundsPhysicsAnimationEnd() {
+ // The physics animation ended, though we may not necessarily be done animating, such as
+ // when we're still dragging after moving out of the magnetic target.
if (!mDismissalPending
&& !mSpringingToTouch
&& !mMagnetizedPip.getObjectStuckToTarget()) {
- mPipBoundsState.setBounds(mTemporaryBounds);
+ // All animations (including dragging) have actually finished.
+ mPipBoundsState.setBounds(
+ mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds());
+ mPipBoundsState.getAnimatingBoundsState().onAllAnimationsEnded();
if (!mDismissalPending) {
// do not schedule resize if PiP is dismissing, which may cause app re-open to
// mBounds instead of it's normal bounds.
mPipTaskOrganizer.scheduleFinishResizePip(getBounds());
}
- mTemporaryBounds.setEmpty();
}
-
- mAnimatingToBounds.setEmpty();
+ mPipBoundsState.getAnimatingBoundsState().onPhysicsAnimationEnded();
mSpringingToTouch = false;
mDismissalPending = false;
}
/**
- * Notifies the floating coordinator that we're moving, and sets {@link #mAnimatingToBounds} so
+ * Notifies the floating coordinator that we're moving, and sets the animating to bounds so
* we return these bounds from
* {@link FloatingContentCoordinator.FloatingContent#getFloatingBoundsOnScreen()}.
*/
private void setAnimatingToBounds(Rect bounds) {
- mAnimatingToBounds.set(bounds);
+ mPipBoundsState.getAnimatingBoundsState().setAnimatingToBounds(bounds);
mFloatingContentCoordinator.onContentMoved(this);
}
@@ -639,7 +617,8 @@
MagnetizedObject<Rect> getMagnetizedPip() {
if (mMagnetizedPip == null) {
mMagnetizedPip = new MagnetizedObject<Rect>(
- mContext, mTemporaryBounds, FloatProperties.RECT_X, FloatProperties.RECT_Y) {
+ mContext, mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds(),
+ FloatProperties.RECT_X, FloatProperties.RECT_Y) {
@Override
public float getWidth(@NonNull Rect animatedPipBounds) {
return animatedPipBounds.width();
@@ -661,10 +640,4 @@
return mMagnetizedPip;
}
-
- public void dump(PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- pw.println(prefix + TAG);
- pw.println(innerPrefix + "mBounds=" + getBounds());
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index d820e77..c04e7e8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -710,7 +710,7 @@
return;
}
- Rect bounds = mMotionHelper.getPossiblyAnimatingBounds();
+ Rect bounds = getPossiblyAnimatingBounds();
mDelta.set(0f, 0f);
mStartPosition.set(bounds.left, bounds.top);
mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom;
@@ -747,7 +747,7 @@
mDelta.x += left - lastX;
mDelta.y += top - lastY;
- mTmpBounds.set(mMotionHelper.getPossiblyAnimatingBounds());
+ mTmpBounds.set(getPossiblyAnimatingBounds());
mTmpBounds.offsetTo((int) left, (int) top);
mMotionHelper.movePip(mTmpBounds, true /* isDragging */);
@@ -783,7 +783,7 @@
// Reset the touch state on up before the fling settles
mTouchState.reset();
- final Rect animatingBounds = mMotionHelper.getPossiblyAnimatingBounds();
+ final Rect animatingBounds = getPossiblyAnimatingBounds();
// If User releases the PIP window while it's out of the display bounds, put
// PIP into stashed mode.
if (mEnableStash
@@ -872,6 +872,16 @@
|| mExpandedBounds.height() != mNormalBounds.height();
}
+ /**
+ * Returns the PIP bounds if we're not animating, or the current, temporary animating bounds
+ * otherwise.
+ */
+ Rect getPossiblyAnimatingBounds() {
+ return mPipBoundsState.getAnimatingBoundsState().isAnimating()
+ ? mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds()
+ : mPipBoundsState.getBounds();
+ }
+
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
@@ -889,7 +899,6 @@
pw.println(innerPrefix + "mMovementBoundsExtraOffsets=" + mMovementBoundsExtraOffsets);
mPipBoundsHandler.dump(pw, innerPrefix);
mTouchState.dump(pw, innerPrefix);
- mMotionHelper.dump(pw, innerPrefix);
if (mPipResizeGestureHandler != null) {
mPipResizeGestureHandler.dump(pw, innerPrefix);
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 6b974ff..db0eed8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -45,6 +45,15 @@
}
}
+fun WmAssertion.visibleWindowsShownMoreThanOneConsecutiveEntry(
+ bugId: Int = 0,
+ enabled: Boolean = bugId == 0
+) {
+ all("visibleWindowShownMoreThanOneConsecutiveEntry", bugId, enabled) {
+ this.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
+}
+
@JvmOverloads
fun LayersAssertion.noUncoveredRegions(
beginRotation: Int,
@@ -159,6 +168,15 @@
}
}
+fun LayersAssertion.visibleLayersShownMoreThanOneConsecutiveEntry(
+ bugId: Int = 0,
+ enabled: Boolean = bugId == 0
+) {
+ all("visibleLayersShownMoreThanOneConsecutiveEntry", bugId, enabled) {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+}
+
fun EventLogAssertion.focusChanges(
vararg windows: String,
bugId: Int = 0,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt
index d31c4ba..72efdb1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt
@@ -43,12 +43,12 @@
}
}
-fun LayersAssertion.wallpaperLayerBecomesInvisible(
+fun LayersAssertion.appLayerReplacesWallpaperLayer(
testApp: IAppHelper,
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
- all("wallpaperLayerBecomesInvisible", bugId, enabled) {
+ all("appLayerReplacesWallpaperLayer", bugId, enabled) {
this.showsLayer("Wallpaper")
.then()
.replaceVisibleLayer("Wallpaper", testApp.getPackage())
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index ad23d9f..1f03c4d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -38,6 +38,8 @@
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -87,6 +89,7 @@
windowManagerTrace {
navBarWindowIsAlwaysVisible()
statusBarWindowIsAlwaysVisible()
+ visibleWindowsShownMoreThanOneConsecutiveEntry()
appWindowReplacesLauncherAsTopWindow(testApp)
wallpaperWindowBecomesInvisible()
@@ -102,8 +105,10 @@
configuration.endRotation)
navBarLayerIsAlwaysVisible(enabled = false)
statusBarLayerIsAlwaysVisible(enabled = false)
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ enabled = Surface.ROTATION_0 == configuration.endRotation)
- wallpaperLayerBecomesInvisible(testApp)
+ appLayerReplacesWallpaperLayer(testApp)
}
eventLog {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index 5886a61..9b4223a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -38,6 +38,8 @@
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -91,9 +93,10 @@
windowManagerTrace {
navBarWindowIsAlwaysVisible()
statusBarWindowIsAlwaysVisible()
+ visibleWindowsShownMoreThanOneConsecutiveEntry()
appWindowReplacesLauncherAsTopWindow(testApp)
- wallpaperWindowBecomesInvisible(enabled = false)
+ wallpaperWindowBecomesInvisible()
}
layersTrace {
@@ -106,8 +109,10 @@
configuration.endRotation)
navBarLayerIsAlwaysVisible(enabled = false)
statusBarLayerIsAlwaysVisible(enabled = false)
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ enabled = Surface.ROTATION_0 == configuration.endRotation)
- wallpaperLayerBecomesInvisible(testApp)
+ appLayerReplacesWallpaperLayer(testApp)
}
eventLog {