Merge "Clean up the legacy split screen (9/n)" into tm-qpr-dev

This commit is contained in:
Tony Huang
2022-05-10 04:03:06 +00:00
committed by Android (Google) Code Review
44 changed files with 5 additions and 7443 deletions

View File

@@ -483,8 +483,6 @@ package android.app {
field public static final int WINDOWING_MODE_FULLSCREEN = 1; // 0x1
field public static final int WINDOWING_MODE_MULTI_WINDOW = 6; // 0x6
field public static final int WINDOWING_MODE_PINNED = 2; // 0x2
field public static final int WINDOWING_MODE_SPLIT_SCREEN_PRIMARY = 3; // 0x3
field public static final int WINDOWING_MODE_SPLIT_SCREEN_SECONDARY = 4; // 0x4
field public static final int WINDOWING_MODE_UNDEFINED = 0; // 0x0
}

View File

@@ -104,19 +104,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
public static final int WINDOWING_MODE_FULLSCREEN = 1;
/** Always on-top (always visible). of other siblings in its parent container. */
public static final int WINDOWING_MODE_PINNED = 2;
/** The primary container driving the screen to be in split-screen mode. */
// TODO: Remove once split-screen is migrated to wm-shell.
public static final int WINDOWING_MODE_SPLIT_SCREEN_PRIMARY = 3;
/**
* The containers adjacent to the {@link #WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} container in
* split-screen mode.
* NOTE: Containers launched with the windowing mode with APIs like
* {@link ActivityOptions#setLaunchWindowingMode(int)} will be launched in
* {@link #WINDOWING_MODE_FULLSCREEN} if the display isn't currently in split-screen windowing
* mode
*/
// TODO: Remove once split-screen is migrated to wm-shell.
public static final int WINDOWING_MODE_SPLIT_SCREEN_SECONDARY = 4;
/** Can be freely resized within its parent container. */
// TODO: Remove once freeform is migrated to wm-shell.
public static final int WINDOWING_MODE_FREEFORM = 5;
@@ -129,8 +116,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
WINDOWING_MODE_FULLSCREEN,
WINDOWING_MODE_MULTI_WINDOW,
WINDOWING_MODE_PINNED,
WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
WINDOWING_MODE_FREEFORM,
})
public @interface WindowingMode {}
@@ -882,9 +867,8 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
}
/**
* Returns true if this container can be put in either
* {@link #WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} or
* {@link #WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} windowing modes based on its current state.
* Returns true if this container can be put in {@link #WINDOWING_MODE_MULTI_WINDOW}
* windowing mode based on its current state.
* @hide
*/
public boolean supportSplitScreenWindowingMode() {
@@ -903,8 +887,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
case WINDOWING_MODE_FULLSCREEN: return "fullscreen";
case WINDOWING_MODE_MULTI_WINDOW: return "multi-window";
case WINDOWING_MODE_PINNED: return "pinned";
case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: return "split-screen-primary";
case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: return "split-screen-secondary";
case WINDOWING_MODE_FREEFORM: return "freeform";
}
return String.valueOf(windowingMode);

View File

@@ -22,7 +22,6 @@ import com.android.wm.shell.apppairs.AppPairsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasksController;
@@ -39,7 +38,6 @@ import java.util.Optional;
public final class ShellCommandHandlerImpl {
private static final String TAG = ShellCommandHandlerImpl.class.getSimpleName();
private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional;
private final Optional<SplitScreenController> mSplitScreenOptional;
private final Optional<Pip> mPipOptional;
private final Optional<OneHandedController> mOneHandedOptional;
@@ -54,7 +52,6 @@ public final class ShellCommandHandlerImpl {
public ShellCommandHandlerImpl(
ShellTaskOrganizer shellTaskOrganizer,
KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<Pip> pipOptional,
Optional<OneHandedController> oneHandedOptional,
@@ -65,7 +62,6 @@ public final class ShellCommandHandlerImpl {
mShellTaskOrganizer = shellTaskOrganizer;
mKidsModeTaskOrganizer = kidsModeTaskOrganizer;
mRecentTasks = recentTasks;
mLegacySplitScreenOptional = legacySplitScreenOptional;
mSplitScreenOptional = splitScreenOptional;
mPipOptional = pipOptional;
mOneHandedOptional = oneHandedOptional;
@@ -84,7 +80,6 @@ public final class ShellCommandHandlerImpl {
pw.println();
pw.println();
mPipOptional.ifPresent(pip -> pip.dump(pw));
mLegacySplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw));
mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw));
mHideDisplayCutout.ifPresent(hideDisplayCutout -> hideDisplayCutout.dump(pw));
pw.println();

View File

@@ -70,8 +70,6 @@ import com.android.wm.shell.fullscreen.FullscreenUnfoldController;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
@@ -562,18 +560,6 @@ public abstract class WMShellBaseModule {
return Optional.empty();
}
// Legacy split (optional feature)
@WMSingleton
@Provides
static Optional<LegacySplitScreen> provideLegacySplitScreen(
Optional<LegacySplitScreenController> splitScreenController) {
return splitScreenController.map((controller) -> controller.asLegacySplitScreen());
}
@BindsOptionalOf
abstract LegacySplitScreenController optionalLegacySplitScreenController();
// App Pairs (optional feature)
@WMSingleton
@@ -710,7 +696,6 @@ public abstract class WMShellBaseModule {
static ShellCommandHandlerImpl provideShellCommandHandlerImpl(
ShellTaskOrganizer shellTaskOrganizer,
KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<Pip> pipOptional,
Optional<OneHandedController> oneHandedOptional,
@@ -719,8 +704,8 @@ public abstract class WMShellBaseModule {
Optional<RecentTasksController> recentTasksOptional,
@ShellMainThread ShellExecutor mainExecutor) {
return new ShellCommandHandlerImpl(shellTaskOrganizer, kidsModeTaskOrganizer,
legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional,
hideDisplayCutout, appPairsOptional, recentTasksOptional, mainExecutor);
splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout,
appPairsOptional, recentTasksOptional, mainExecutor);
}
@WMSingleton

View File

@@ -16,7 +16,6 @@
package com.android.wm.shell.dagger;
import android.animation.AnimationHandler;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.os.Handler;
@@ -42,12 +41,10 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.fullscreen.FullscreenUnfoldController;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
@@ -179,20 +176,6 @@ public class WMShellModule {
recentTasks, stageTaskUnfoldControllerProvider);
}
@WMSingleton
@Provides
static LegacySplitScreenController provideLegacySplitScreen(Context context,
DisplayController displayController, SystemWindows systemWindows,
DisplayImeController displayImeController, TransactionPool transactionPool,
ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
TaskStackListenerImpl taskStackListener, Transitions transitions,
@ShellMainThread ShellExecutor mainExecutor,
@ChoreographerSfVsync AnimationHandler sfVsyncAnimationHandler) {
return new LegacySplitScreenController(context, displayController, systemWindows,
displayImeController, transactionPool, shellTaskOrganizer, syncQueue,
taskStackListener, transitions, mainExecutor, sfVsyncAnimationHandler);
}
@WMSingleton
@Provides
static AppPairsController provideAppPairs(ShellTaskOrganizer shellTaskOrganizer,

View File

@@ -1,418 +0,0 @@
/*
* 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.wm.shell.legacysplitscreen;
import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.graphics.Rect;
import android.util.Slog;
import android.view.Choreographer;
import android.view.SurfaceControl;
import android.window.TaskOrganizer;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
class DividerImeController implements DisplayImeController.ImePositionProcessor {
private static final String TAG = "DividerImeController";
private static final boolean DEBUG = LegacySplitScreenController.DEBUG;
private static final float ADJUSTED_NONFOCUS_DIM = 0.3f;
private final LegacySplitScreenTaskListener mSplits;
private final TransactionPool mTransactionPool;
private final ShellExecutor mMainExecutor;
private final TaskOrganizer mTaskOrganizer;
/**
* These are the y positions of the top of the IME surface when it is hidden and when it is
* shown respectively. These are NOT necessarily the top of the visible IME itself.
*/
private int mHiddenTop = 0;
private int mShownTop = 0;
// The following are target states (what we are curretly animating towards).
/**
* {@code true} if, at the end of the animation, the split task positions should be
* adjusted by height of the IME. This happens when the secondary split is the IME target.
*/
private boolean mTargetAdjusted = false;
/**
* {@code true} if, at the end of the animation, the IME should be shown/visible
* regardless of what has focus.
*/
private boolean mTargetShown = false;
private float mTargetPrimaryDim = 0.f;
private float mTargetSecondaryDim = 0.f;
// The following are the current (most recent) states set during animation
/** {@code true} if the secondary split has IME focus. */
private boolean mSecondaryHasFocus = false;
/** The dimming currently applied to the primary/secondary splits. */
private float mLastPrimaryDim = 0.f;
private float mLastSecondaryDim = 0.f;
/** The most recent y position of the top of the IME surface */
private int mLastAdjustTop = -1;
// The following are states reached last time an animation fully completed.
/** {@code true} if the IME was shown/visible by the last-completed animation. */
private boolean mImeWasShown = false;
/** {@code true} if the split positions were adjusted by the last-completed animation. */
private boolean mAdjusted = false;
/**
* When some aspect of split-screen needs to animate independent from the IME,
* this will be non-null and control split animation.
*/
@Nullable
private ValueAnimator mAnimation = null;
private boolean mPaused = true;
private boolean mPausedTargetAdjusted = false;
DividerImeController(LegacySplitScreenTaskListener splits, TransactionPool pool,
ShellExecutor mainExecutor, TaskOrganizer taskOrganizer) {
mSplits = splits;
mTransactionPool = pool;
mMainExecutor = mainExecutor;
mTaskOrganizer = taskOrganizer;
}
private DividerView getView() {
return mSplits.mSplitScreenController.getDividerView();
}
private LegacySplitDisplayLayout getLayout() {
return mSplits.mSplitScreenController.getSplitLayout();
}
private boolean isDividerHidden() {
final DividerView view = mSplits.mSplitScreenController.getDividerView();
return view == null || view.isHidden();
}
private boolean getSecondaryHasFocus(int displayId) {
WindowContainerToken imeSplit = mTaskOrganizer.getImeTarget(displayId);
return imeSplit != null
&& (imeSplit.asBinder() == mSplits.mSecondary.token.asBinder());
}
void reset() {
mPaused = true;
mPausedTargetAdjusted = false;
mAnimation = null;
mAdjusted = mTargetAdjusted = false;
mImeWasShown = mTargetShown = false;
mTargetPrimaryDim = mTargetSecondaryDim = mLastPrimaryDim = mLastSecondaryDim = 0.f;
mSecondaryHasFocus = false;
mLastAdjustTop = -1;
}
private void updateDimTargets() {
final boolean splitIsVisible = !getView().isHidden();
mTargetPrimaryDim = (mSecondaryHasFocus && mTargetShown && splitIsVisible)
? ADJUSTED_NONFOCUS_DIM : 0.f;
mTargetSecondaryDim = (!mSecondaryHasFocus && mTargetShown && splitIsVisible)
? ADJUSTED_NONFOCUS_DIM : 0.f;
}
@Override
public void onImeControlTargetChanged(int displayId, boolean controlling) {
// Restore the split layout when wm-shell is not controlling IME insets anymore.
if (!controlling && mTargetShown) {
mPaused = false;
mTargetAdjusted = mTargetShown = false;
mTargetPrimaryDim = mTargetSecondaryDim = 0.f;
updateImeAdjustState(true /* force */);
startAsyncAnimation();
}
}
@Override
@ImeAnimationFlags
public int onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
boolean imeShouldShow, boolean imeIsFloating, SurfaceControl.Transaction t) {
if (isDividerHidden()) {
return 0;
}
mHiddenTop = hiddenTop;
mShownTop = shownTop;
mTargetShown = imeShouldShow;
mSecondaryHasFocus = getSecondaryHasFocus(displayId);
final boolean targetAdjusted = imeShouldShow && mSecondaryHasFocus
&& !imeIsFloating && !getLayout().mDisplayLayout.isLandscape()
&& !mSplits.mSplitScreenController.isMinimized();
if (mLastAdjustTop < 0) {
mLastAdjustTop = imeShouldShow ? hiddenTop : shownTop;
} else if (mLastAdjustTop != (imeShouldShow ? mShownTop : mHiddenTop)) {
if (mTargetAdjusted != targetAdjusted && targetAdjusted == mAdjusted) {
// Check for an "interruption" of an existing animation. In this case, we
// need to fake-flip the last-known state direction so that the animation
// completes in the other direction.
mAdjusted = mTargetAdjusted;
} else if (targetAdjusted && mTargetAdjusted && mAdjusted) {
// Already fully adjusted for IME, but IME height has changed; so, force-start
// an async animation to the new IME height.
mAdjusted = false;
}
}
if (mPaused) {
mPausedTargetAdjusted = targetAdjusted;
if (DEBUG) Slog.d(TAG, " ime starting but paused " + dumpState());
return (targetAdjusted || mAdjusted) ? IME_ANIMATION_NO_ALPHA : 0;
}
mTargetAdjusted = targetAdjusted;
updateDimTargets();
if (DEBUG) Slog.d(TAG, " ime starting. " + dumpState());
if (mAnimation != null || (mImeWasShown && imeShouldShow
&& mTargetAdjusted != mAdjusted)) {
// We need to animate adjustment independently of the IME position, so
// start our own animation to drive adjustment. This happens when a
// different split's editor has gained focus while the IME is still visible.
startAsyncAnimation();
}
updateImeAdjustState();
return (mTargetAdjusted || mAdjusted) ? IME_ANIMATION_NO_ALPHA : 0;
}
private void updateImeAdjustState() {
updateImeAdjustState(false /* force */);
}
private void updateImeAdjustState(boolean force) {
if (mAdjusted != mTargetAdjusted || force) {
// Reposition the server's secondary split position so that it evaluates
// insets properly.
WindowContainerTransaction wct = new WindowContainerTransaction();
final LegacySplitDisplayLayout splitLayout = getLayout();
if (mTargetAdjusted) {
splitLayout.updateAdjustedBounds(mShownTop, mHiddenTop, mShownTop);
wct.setBounds(mSplits.mSecondary.token, splitLayout.mAdjustedSecondary);
// "Freeze" the configuration size so that the app doesn't get a config
// or relaunch. This is required because normally nav-bar contributes
// to configuration bounds (via nondecorframe).
Rect adjustAppBounds = new Rect(mSplits.mSecondary.configuration
.windowConfiguration.getAppBounds());
adjustAppBounds.offset(0, splitLayout.mAdjustedSecondary.top
- splitLayout.mSecondary.top);
wct.setAppBounds(mSplits.mSecondary.token, adjustAppBounds);
wct.setScreenSizeDp(mSplits.mSecondary.token,
mSplits.mSecondary.configuration.screenWidthDp,
mSplits.mSecondary.configuration.screenHeightDp);
wct.setBounds(mSplits.mPrimary.token, splitLayout.mAdjustedPrimary);
adjustAppBounds = new Rect(mSplits.mPrimary.configuration
.windowConfiguration.getAppBounds());
adjustAppBounds.offset(0, splitLayout.mAdjustedPrimary.top
- splitLayout.mPrimary.top);
wct.setAppBounds(mSplits.mPrimary.token, adjustAppBounds);
wct.setScreenSizeDp(mSplits.mPrimary.token,
mSplits.mPrimary.configuration.screenWidthDp,
mSplits.mPrimary.configuration.screenHeightDp);
} else {
wct.setBounds(mSplits.mSecondary.token, splitLayout.mSecondary);
wct.setAppBounds(mSplits.mSecondary.token, null);
wct.setScreenSizeDp(mSplits.mSecondary.token,
SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
wct.setBounds(mSplits.mPrimary.token, splitLayout.mPrimary);
wct.setAppBounds(mSplits.mPrimary.token, null);
wct.setScreenSizeDp(mSplits.mPrimary.token,
SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
}
if (!mSplits.mSplitScreenController.getWmProxy().queueSyncTransactionIfWaiting(wct)) {
mTaskOrganizer.applyTransaction(wct);
}
}
// Update all the adjusted-for-ime states
if (!mPaused) {
final DividerView view = getView();
if (view != null) {
view.setAdjustedForIme(mTargetShown, mTargetShown
? DisplayImeController.ANIMATION_DURATION_SHOW_MS
: DisplayImeController.ANIMATION_DURATION_HIDE_MS);
}
}
mSplits.mSplitScreenController.setAdjustedForIme(mTargetShown && !mPaused);
}
@Override
public void onImePositionChanged(int displayId, int imeTop,
SurfaceControl.Transaction t) {
if (mAnimation != null || isDividerHidden() || mPaused) {
// Not synchronized with IME anymore, so return.
return;
}
final float fraction = ((float) imeTop - mHiddenTop) / (mShownTop - mHiddenTop);
final float progress = mTargetShown ? fraction : 1.f - fraction;
onProgress(progress, t);
}
@Override
public void onImeEndPositioning(int displayId, boolean cancelled,
SurfaceControl.Transaction t) {
if (mAnimation != null || isDividerHidden() || mPaused) {
// Not synchronized with IME anymore, so return.
return;
}
onEnd(cancelled, t);
}
private void onProgress(float progress, SurfaceControl.Transaction t) {
final DividerView view = getView();
if (mTargetAdjusted != mAdjusted && !mPaused) {
final LegacySplitDisplayLayout splitLayout = getLayout();
final float fraction = mTargetAdjusted ? progress : 1.f - progress;
mLastAdjustTop = (int) (fraction * mShownTop + (1.f - fraction) * mHiddenTop);
splitLayout.updateAdjustedBounds(mLastAdjustTop, mHiddenTop, mShownTop);
view.resizeSplitSurfaces(t, splitLayout.mAdjustedPrimary,
splitLayout.mAdjustedSecondary);
}
final float invProg = 1.f - progress;
view.setResizeDimLayer(t, true /* primary */,
mLastPrimaryDim * invProg + progress * mTargetPrimaryDim);
view.setResizeDimLayer(t, false /* primary */,
mLastSecondaryDim * invProg + progress * mTargetSecondaryDim);
}
void setDimsHidden(SurfaceControl.Transaction t, boolean hidden) {
final DividerView view = getView();
if (hidden) {
view.setResizeDimLayer(t, true /* primary */, 0.f /* alpha */);
view.setResizeDimLayer(t, false /* primary */, 0.f /* alpha */);
} else {
updateDimTargets();
view.setResizeDimLayer(t, true /* primary */, mTargetPrimaryDim);
view.setResizeDimLayer(t, false /* primary */, mTargetSecondaryDim);
}
}
private void onEnd(boolean cancelled, SurfaceControl.Transaction t) {
if (!cancelled) {
onProgress(1.f, t);
mAdjusted = mTargetAdjusted;
mImeWasShown = mTargetShown;
mLastAdjustTop = mAdjusted ? mShownTop : mHiddenTop;
mLastPrimaryDim = mTargetPrimaryDim;
mLastSecondaryDim = mTargetSecondaryDim;
}
}
private void startAsyncAnimation() {
if (mAnimation != null) {
mAnimation.cancel();
}
mAnimation = ValueAnimator.ofFloat(0.f, 1.f);
mAnimation.setDuration(DisplayImeController.ANIMATION_DURATION_SHOW_MS);
if (mTargetAdjusted != mAdjusted) {
final float fraction =
((float) mLastAdjustTop - mHiddenTop) / (mShownTop - mHiddenTop);
final float progress = mTargetAdjusted ? fraction : 1.f - fraction;
mAnimation.setCurrentFraction(progress);
}
mAnimation.addUpdateListener(animation -> {
SurfaceControl.Transaction t = mTransactionPool.acquire();
float value = (float) animation.getAnimatedValue();
onProgress(value, t);
t.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
t.apply();
mTransactionPool.release(t);
});
mAnimation.setInterpolator(DisplayImeController.INTERPOLATOR);
mAnimation.addListener(new AnimatorListenerAdapter() {
private boolean mCancel = false;
@Override
public void onAnimationCancel(Animator animation) {
mCancel = true;
}
@Override
public void onAnimationEnd(Animator animation) {
SurfaceControl.Transaction t = mTransactionPool.acquire();
onEnd(mCancel, t);
t.apply();
mTransactionPool.release(t);
mAnimation = null;
}
});
mAnimation.start();
}
private String dumpState() {
return "top:" + mHiddenTop + "->" + mShownTop
+ " adj:" + mAdjusted + "->" + mTargetAdjusted + "(" + mLastAdjustTop + ")"
+ " shw:" + mImeWasShown + "->" + mTargetShown
+ " dims:" + mLastPrimaryDim + "," + mLastSecondaryDim
+ "->" + mTargetPrimaryDim + "," + mTargetSecondaryDim
+ " shf:" + mSecondaryHasFocus + " desync:" + (mAnimation != null)
+ " paus:" + mPaused + "[" + mPausedTargetAdjusted + "]";
}
/** Completely aborts/resets adjustment state */
public void pause(int displayId) {
if (DEBUG) Slog.d(TAG, "ime pause posting " + dumpState());
mMainExecutor.execute(() -> {
if (DEBUG) Slog.d(TAG, "ime pause run posted " + dumpState());
if (mPaused) {
return;
}
mPaused = true;
mPausedTargetAdjusted = mTargetAdjusted;
mTargetAdjusted = false;
mTargetPrimaryDim = mTargetSecondaryDim = 0.f;
updateImeAdjustState();
startAsyncAnimation();
if (mAnimation != null) {
mAnimation.end();
}
});
}
public void resume(int displayId) {
if (DEBUG) Slog.d(TAG, "ime resume posting " + dumpState());
mMainExecutor.execute(() -> {
if (DEBUG) Slog.d(TAG, "ime resume run posted " + dumpState());
if (!mPaused) {
return;
}
mPaused = false;
mTargetAdjusted = mPausedTargetAdjusted;
updateDimTargets();
final DividerView view = getView();
if ((mTargetAdjusted != mAdjusted) && !mSplits.mSplitScreenController.isMinimized()
&& view != null) {
// End unminimize animations since they conflict with adjustment animations.
view.finishAnimations();
}
updateImeAdjustState();
startAsyncAnimation();
});
}
}

View File

@@ -1,25 +0,0 @@
/*
* Copyright (C) 2016 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.legacysplitscreen;
/**
* Class to hold state of divider that needs to persist across configuration changes.
*/
final class DividerState {
public boolean animateAfterRecentsDrawn;
public float mRatioPositionBeforeMinimized;
}

View File

@@ -1,117 +0,0 @@
/*
* Copyright (C) 2015 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.legacysplitscreen;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.SHELL_ROOT_LAYER_DIVIDER;
import android.graphics.PixelFormat;
import android.graphics.Region;
import android.os.Binder;
import android.view.View;
import android.view.WindowManager;
import com.android.wm.shell.common.SystemWindows;
/**
* Manages the window parameters of the docked stack divider.
*/
final class DividerWindowManager {
private static final String WINDOW_TITLE = "DockedStackDivider";
final SystemWindows mSystemWindows;
private WindowManager.LayoutParams mLp;
private View mView;
DividerWindowManager(SystemWindows systemWindows) {
mSystemWindows = systemWindows;
}
/** Add a divider view */
void add(View view, int width, int height, int displayId) {
mLp = new WindowManager.LayoutParams(
width, height, TYPE_DOCK_DIVIDER,
FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL
| FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT);
mLp.token = new Binder();
mLp.setTitle(WINDOW_TITLE);
mLp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
mSystemWindows.addView(view, mLp, displayId, SHELL_ROOT_LAYER_DIVIDER);
mView = view;
}
void remove() {
if (mView != null) {
mSystemWindows.removeView(mView);
}
mView = null;
}
void setSlippery(boolean slippery) {
boolean changed = false;
if (slippery && (mLp.flags & FLAG_SLIPPERY) == 0) {
mLp.flags |= FLAG_SLIPPERY;
changed = true;
} else if (!slippery && (mLp.flags & FLAG_SLIPPERY) != 0) {
mLp.flags &= ~FLAG_SLIPPERY;
changed = true;
}
if (changed) {
mSystemWindows.updateViewLayout(mView, mLp);
}
}
void setTouchable(boolean touchable) {
if (mView == null) {
return;
}
boolean changed = false;
if (!touchable && (mLp.flags & FLAG_NOT_TOUCHABLE) == 0) {
mLp.flags |= FLAG_NOT_TOUCHABLE;
changed = true;
} else if (touchable && (mLp.flags & FLAG_NOT_TOUCHABLE) != 0) {
mLp.flags &= ~FLAG_NOT_TOUCHABLE;
changed = true;
}
if (changed) {
mSystemWindows.updateViewLayout(mView, mLp);
}
}
/** Sets the touch region to `touchRegion`. Use null to unset.*/
void setTouchRegion(Region touchRegion) {
if (mView == null) {
return;
}
mSystemWindows.setTouchableRegion(mView, touchRegion);
}
}

View File

@@ -1,110 +0,0 @@
/*
* Copyright (C) 2016 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.legacysplitscreen;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.TextView;
import com.android.wm.shell.R;
/**
* Translucent activity that gets started on top of a task in multi-window to inform the user that
* we forced the activity below to be resizable.
*
* Note: This activity runs on the main thread of the process hosting the Shell lib.
*/
public class ForcedResizableInfoActivity extends Activity implements OnTouchListener {
public static final String EXTRA_FORCED_RESIZEABLE_REASON = "extra_forced_resizeable_reason";
private static final long DISMISS_DELAY = 2500;
private final Runnable mFinishRunnable = new Runnable() {
@Override
public void run() {
finish();
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.forced_resizable_activity);
TextView tv = findViewById(com.android.internal.R.id.message);
int reason = getIntent().getIntExtra(EXTRA_FORCED_RESIZEABLE_REASON, -1);
String text;
switch (reason) {
case FORCED_RESIZEABLE_REASON_SPLIT_SCREEN:
text = getString(R.string.dock_forced_resizable);
break;
case FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY:
text = getString(R.string.forced_resizable_secondary_display);
break;
default:
throw new IllegalArgumentException("Unexpected forced resizeable reason: "
+ reason);
}
tv.setText(text);
getWindow().setTitle(text);
getWindow().getDecorView().setOnTouchListener(this);
}
@Override
protected void onStart() {
super.onStart();
getWindow().getDecorView().postDelayed(mFinishRunnable, DISMISS_DELAY);
}
@Override
protected void onStop() {
super.onStop();
finish();
}
@Override
public boolean onTouch(View v, MotionEvent event) {
finish();
return true;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
finish();
return true;
}
@Override
public void finish() {
super.finish();
overridePendingTransition(0, R.anim.forced_resizable_exit);
}
@Override
public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
// Do nothing
}
}

View File

@@ -1,150 +0,0 @@
/*
* Copyright (C) 2016 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.legacysplitscreen;
import static com.android.wm.shell.legacysplitscreen.ForcedResizableInfoActivity.EXTRA_FORCED_RESIZEABLE_REASON;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
import android.os.UserHandle;
import android.util.ArraySet;
import android.widget.Toast;
import com.android.wm.shell.R;
import com.android.wm.shell.common.ShellExecutor;
import java.util.function.Consumer;
/**
* Controller that decides when to show the {@link ForcedResizableInfoActivity}.
*/
final class ForcedResizableInfoActivityController implements DividerView.DividerCallbacks {
private static final String SELF_PACKAGE_NAME = "com.android.systemui";
private static final int TIMEOUT = 1000;
private final Context mContext;
private final ShellExecutor mMainExecutor;
private final ArraySet<PendingTaskRecord> mPendingTasks = new ArraySet<>();
private final ArraySet<String> mPackagesShownInSession = new ArraySet<>();
private boolean mDividerDragging;
private final Runnable mTimeoutRunnable = this::showPending;
private final Consumer<Boolean> mDockedStackExistsListener = exists -> {
if (!exists) {
mPackagesShownInSession.clear();
}
};
/** Record of force resized task that's pending to be handled. */
private class PendingTaskRecord {
int mTaskId;
/**
* {@link android.app.ITaskStackListener#FORCED_RESIZEABLE_REASON_SPLIT_SCREEN} or
* {@link android.app.ITaskStackListener#FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY}
*/
int mReason;
PendingTaskRecord(int taskId, int reason) {
this.mTaskId = taskId;
this.mReason = reason;
}
}
ForcedResizableInfoActivityController(Context context,
LegacySplitScreenController splitScreenController,
ShellExecutor mainExecutor) {
mContext = context;
mMainExecutor = mainExecutor;
splitScreenController.registerInSplitScreenListener(mDockedStackExistsListener);
}
@Override
public void onDraggingStart() {
mDividerDragging = true;
mMainExecutor.removeCallbacks(mTimeoutRunnable);
}
@Override
public void onDraggingEnd() {
mDividerDragging = false;
showPending();
}
void onAppTransitionFinished() {
if (!mDividerDragging) {
showPending();
}
}
void activityForcedResizable(String packageName, int taskId, int reason) {
if (debounce(packageName)) {
return;
}
mPendingTasks.add(new PendingTaskRecord(taskId, reason));
postTimeout();
}
void activityDismissingSplitScreen() {
Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text,
Toast.LENGTH_SHORT).show();
}
void activityLaunchOnSecondaryDisplayFailed() {
Toast.makeText(mContext, R.string.activity_launch_on_secondary_display_failed_text,
Toast.LENGTH_SHORT).show();
}
private void showPending() {
mMainExecutor.removeCallbacks(mTimeoutRunnable);
for (int i = mPendingTasks.size() - 1; i >= 0; i--) {
PendingTaskRecord pendingRecord = mPendingTasks.valueAt(i);
Intent intent = new Intent(mContext, ForcedResizableInfoActivity.class);
ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchTaskId(pendingRecord.mTaskId);
// Set as task overlay and allow to resume, so that when an app enters split-screen and
// becomes paused, the overlay will still be shown.
options.setTaskOverlay(true, true /* canResume */);
intent.putExtra(EXTRA_FORCED_RESIZEABLE_REASON, pendingRecord.mReason);
mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT);
}
mPendingTasks.clear();
}
private void postTimeout() {
mMainExecutor.removeCallbacks(mTimeoutRunnable);
mMainExecutor.executeDelayed(mTimeoutRunnable, TIMEOUT);
}
private boolean debounce(String packageName) {
if (packageName == null) {
return false;
}
// We launch ForcedResizableInfoActivity into a task that was forced resizable, so that
// triggers another notification. So ignore our own activity.
if (SELF_PACKAGE_NAME.equals(packageName)) {
return true;
}
boolean debounce = mPackagesShownInSession.contains(packageName);
mPackagesShownInSession.add(packageName);
return debounce;
}
}

View File

@@ -1,326 +0,0 @@
/*
* 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.wm.shell.legacysplitscreen;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.util.RotationUtils.rotateBounds;
import static android.view.WindowManager.DOCKED_BOTTOM;
import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
import static android.view.WindowManager.DOCKED_TOP;
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.util.TypedValue;
import android.window.WindowContainerTransaction;
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.internal.policy.DockedDividerUtils;
import com.android.wm.shell.common.DisplayLayout;
/**
* Handles split-screen related internal display layout. In general, this represents the
* WM-facing understanding of the splits.
*/
public class LegacySplitDisplayLayout {
/** Minimum size of an adjusted stack bounds relative to original stack bounds. Used to
* restrict IME adjustment so that a min portion of top stack remains visible.*/
private static final float ADJUSTED_STACK_FRACTION_MIN = 0.3f;
private static final int DIVIDER_WIDTH_INACTIVE_DP = 4;
LegacySplitScreenTaskListener mTiles;
DisplayLayout mDisplayLayout;
Context mContext;
// Lazy stuff
boolean mResourcesValid = false;
int mDividerSize;
int mDividerSizeInactive;
private DividerSnapAlgorithm mSnapAlgorithm = null;
private DividerSnapAlgorithm mMinimizedSnapAlgorithm = null;
Rect mPrimary = null;
Rect mSecondary = null;
Rect mAdjustedPrimary = null;
Rect mAdjustedSecondary = null;
final Rect mTmpBounds = new Rect();
public LegacySplitDisplayLayout(Context ctx, DisplayLayout dl,
LegacySplitScreenTaskListener taskTiles) {
mTiles = taskTiles;
mDisplayLayout = dl;
mContext = ctx;
}
void rotateTo(int newRotation) {
mDisplayLayout.rotateTo(mContext.getResources(), newRotation);
final Configuration config = new Configuration();
config.unset();
config.orientation = mDisplayLayout.getOrientation();
Rect tmpRect = new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
tmpRect.inset(mDisplayLayout.nonDecorInsets());
config.windowConfiguration.setAppBounds(tmpRect);
tmpRect.set(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
tmpRect.inset(mDisplayLayout.stableInsets());
config.screenWidthDp = (int) (tmpRect.width() / mDisplayLayout.density());
config.screenHeightDp = (int) (tmpRect.height() / mDisplayLayout.density());
mContext = mContext.createConfigurationContext(config);
mSnapAlgorithm = null;
mMinimizedSnapAlgorithm = null;
mResourcesValid = false;
}
private void updateResources() {
if (mResourcesValid) {
return;
}
mResourcesValid = true;
Resources res = mContext.getResources();
mDividerSize = DockedDividerUtils.getDividerSize(res,
DockedDividerUtils.getDividerInsets(res));
mDividerSizeInactive = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, DIVIDER_WIDTH_INACTIVE_DP, res.getDisplayMetrics());
}
int getPrimarySplitSide() {
switch (mDisplayLayout.getNavigationBarPosition(mContext.getResources())) {
case DisplayLayout.NAV_BAR_BOTTOM:
return mDisplayLayout.isLandscape() ? DOCKED_LEFT : DOCKED_TOP;
case DisplayLayout.NAV_BAR_LEFT:
return DOCKED_RIGHT;
case DisplayLayout.NAV_BAR_RIGHT:
return DOCKED_LEFT;
default:
return DOCKED_INVALID;
}
}
DividerSnapAlgorithm getSnapAlgorithm() {
if (mSnapAlgorithm == null) {
updateResources();
boolean isHorizontalDivision = !mDisplayLayout.isLandscape();
mSnapAlgorithm = new DividerSnapAlgorithm(mContext.getResources(),
mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize,
isHorizontalDivision, mDisplayLayout.stableInsets(), getPrimarySplitSide());
}
return mSnapAlgorithm;
}
DividerSnapAlgorithm getMinimizedSnapAlgorithm(boolean homeStackResizable) {
if (mMinimizedSnapAlgorithm == null) {
updateResources();
boolean isHorizontalDivision = !mDisplayLayout.isLandscape();
mMinimizedSnapAlgorithm = new DividerSnapAlgorithm(mContext.getResources(),
mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize,
isHorizontalDivision, mDisplayLayout.stableInsets(), getPrimarySplitSide(),
true /* isMinimized */, homeStackResizable);
}
return mMinimizedSnapAlgorithm;
}
/**
* Resize primary bounds and secondary bounds by divider position.
*
* @param position divider position.
* @return true if calculated bounds changed.
*/
boolean resizeSplits(int position) {
mPrimary = mPrimary == null ? new Rect() : mPrimary;
mSecondary = mSecondary == null ? new Rect() : mSecondary;
int dockSide = getPrimarySplitSide();
boolean boundsChanged;
mTmpBounds.set(mPrimary);
DockedDividerUtils.calculateBoundsForPosition(position, dockSide, mPrimary,
mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize);
boundsChanged = !mPrimary.equals(mTmpBounds);
mTmpBounds.set(mSecondary);
DockedDividerUtils.calculateBoundsForPosition(position,
DockedDividerUtils.invertDockSide(dockSide), mSecondary, mDisplayLayout.width(),
mDisplayLayout.height(), mDividerSize);
boundsChanged |= !mSecondary.equals(mTmpBounds);
return boundsChanged;
}
void resizeSplits(int position, WindowContainerTransaction t) {
if (resizeSplits(position)) {
t.setBounds(mTiles.mPrimary.token, mPrimary);
t.setBounds(mTiles.mSecondary.token, mSecondary);
t.setSmallestScreenWidthDp(mTiles.mPrimary.token,
getSmallestWidthDpForBounds(mContext, mDisplayLayout, mPrimary));
t.setSmallestScreenWidthDp(mTiles.mSecondary.token,
getSmallestWidthDpForBounds(mContext, mDisplayLayout, mSecondary));
}
}
Rect calcResizableMinimizedHomeStackBounds() {
DividerSnapAlgorithm.SnapTarget miniMid =
getMinimizedSnapAlgorithm(true /* resizable */).getMiddleTarget();
Rect homeBounds = new Rect();
DockedDividerUtils.calculateBoundsForPosition(miniMid.position,
DockedDividerUtils.invertDockSide(getPrimarySplitSide()), homeBounds,
mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize);
return homeBounds;
}
/**
* Updates the adjustment depending on it's current state.
*/
void updateAdjustedBounds(int currImeTop, int hiddenTop, int shownTop) {
adjustForIME(mDisplayLayout, currImeTop, hiddenTop, shownTop, mDividerSize,
mDividerSizeInactive, mPrimary, mSecondary);
}
/** Assumes top/bottom split. Splits are not adjusted for left/right splits. */
private void adjustForIME(DisplayLayout dl, int currImeTop, int hiddenTop, int shownTop,
int dividerWidth, int dividerWidthInactive, Rect primaryBounds, Rect secondaryBounds) {
if (mAdjustedPrimary == null) {
mAdjustedPrimary = new Rect();
mAdjustedSecondary = new Rect();
}
final Rect displayStableRect = new Rect();
dl.getStableBounds(displayStableRect);
final float shownFraction = ((float) (currImeTop - hiddenTop)) / (shownTop - hiddenTop);
final int currDividerWidth =
(int) (dividerWidthInactive * shownFraction + dividerWidth * (1.f - shownFraction));
// Calculate the highest we can move the bottom of the top stack to keep 30% visible.
final int minTopStackBottom = displayStableRect.top
+ (int) ((mPrimary.bottom - displayStableRect.top) * ADJUSTED_STACK_FRACTION_MIN);
// Based on that, calculate the maximum amount we'll allow the ime to shift things.
final int maxOffset = mPrimary.bottom - minTopStackBottom;
// Calculate how much we would shift things without limits (basically the height of ime).
final int desiredOffset = hiddenTop - shownTop;
// Calculate an "adjustedTop" which is the currImeTop but restricted by our constraints.
// We want an effect where the adjustment only occurs during the "highest" portion of the
// ime animation. This is done by shifting the adjustment values by the difference in
// offsets (effectively playing the whole adjustment animation some fixed amount of pixels
// below the ime top).
final int topCorrection = Math.max(0, desiredOffset - maxOffset);
final int adjustedTop = currImeTop + topCorrection;
// The actual yOffset is the distance between adjustedTop and the bottom of the display.
// Since our adjustedTop values are playing "below" the ime, we clamp at 0 so we only
// see adjustment upward.
final int yOffset = Math.max(0, dl.height() - adjustedTop);
// TOP
// Reduce the offset by an additional small amount to squish the divider bar.
mAdjustedPrimary.set(primaryBounds);
mAdjustedPrimary.offset(0, -yOffset + (dividerWidth - currDividerWidth));
// BOTTOM
mAdjustedSecondary.set(secondaryBounds);
mAdjustedSecondary.offset(0, -yOffset);
}
static int getSmallestWidthDpForBounds(@NonNull Context context, DisplayLayout dl,
Rect bounds) {
int dividerSize = DockedDividerUtils.getDividerSize(context.getResources(),
DockedDividerUtils.getDividerInsets(context.getResources()));
int minWidth = Integer.MAX_VALUE;
// Go through all screen orientations and find the orientation in which the task has the
// smallest width.
Rect tmpRect = new Rect();
Rect rotatedDisplayRect = new Rect();
Rect displayRect = new Rect(0, 0, dl.width(), dl.height());
DisplayLayout tmpDL = new DisplayLayout();
for (int rotation = 0; rotation < 4; rotation++) {
tmpDL.set(dl);
tmpDL.rotateTo(context.getResources(), rotation);
DividerSnapAlgorithm snap = initSnapAlgorithmForRotation(context, tmpDL, dividerSize);
tmpRect.set(bounds);
rotateBounds(tmpRect, displayRect, dl.rotation(), rotation);
rotatedDisplayRect.set(0, 0, tmpDL.width(), tmpDL.height());
final int dockSide = getPrimarySplitSide(tmpRect, rotatedDisplayRect,
tmpDL.getOrientation());
final int position = DockedDividerUtils.calculatePositionForBounds(tmpRect, dockSide,
dividerSize);
final int snappedPosition =
snap.calculateNonDismissingSnapTarget(position).position;
DockedDividerUtils.calculateBoundsForPosition(snappedPosition, dockSide, tmpRect,
tmpDL.width(), tmpDL.height(), dividerSize);
Rect insettedDisplay = new Rect(rotatedDisplayRect);
insettedDisplay.inset(tmpDL.stableInsets());
tmpRect.intersect(insettedDisplay);
minWidth = Math.min(tmpRect.width(), minWidth);
}
return (int) (minWidth / dl.density());
}
static DividerSnapAlgorithm initSnapAlgorithmForRotation(Context context, DisplayLayout dl,
int dividerSize) {
final Configuration config = new Configuration();
config.unset();
config.orientation = dl.getOrientation();
Rect tmpRect = new Rect(0, 0, dl.width(), dl.height());
tmpRect.inset(dl.nonDecorInsets());
config.windowConfiguration.setAppBounds(tmpRect);
tmpRect.set(0, 0, dl.width(), dl.height());
tmpRect.inset(dl.stableInsets());
config.screenWidthDp = (int) (tmpRect.width() / dl.density());
config.screenHeightDp = (int) (tmpRect.height() / dl.density());
final Context rotationContext = context.createConfigurationContext(config);
return new DividerSnapAlgorithm(
rotationContext.getResources(), dl.width(), dl.height(), dividerSize,
config.orientation == ORIENTATION_PORTRAIT, dl.stableInsets());
}
/**
* Get the current primary-split side. Determined by its location of {@param bounds} within
* {@param displayRect} but if both are the same, it will try to dock to each side and determine
* if allowed in its respected {@param orientation}.
*
* @param bounds bounds of the primary split task to get which side is docked
* @param displayRect bounds of the display that contains the primary split task
* @param orientation the origination of device
* @return current primary-split side
*/
static int getPrimarySplitSide(Rect bounds, Rect displayRect, int orientation) {
if (orientation == ORIENTATION_PORTRAIT) {
// Portrait mode, docked either at the top or the bottom.
final int diff = (displayRect.bottom - bounds.bottom) - (bounds.top - displayRect.top);
if (diff < 0) {
return DOCKED_BOTTOM;
} else {
// Top is default
return DOCKED_TOP;
}
} else if (orientation == ORIENTATION_LANDSCAPE) {
// Landscape mode, docked either on the left or on the right.
final int diff = (displayRect.right - bounds.right) - (bounds.left - displayRect.left);
if (diff < 0) {
return DOCKED_RIGHT;
}
return DOCKED_LEFT;
}
return DOCKED_INVALID;
}
}

View File

@@ -1,85 +0,0 @@
/*
* 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.wm.shell.legacysplitscreen;
import android.graphics.Rect;
import android.window.WindowContainerToken;
import com.android.wm.shell.common.annotations.ExternalThread;
import java.io.PrintWriter;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
* Interface to engage split screen feature.
*/
@ExternalThread
public interface LegacySplitScreen {
/** Called when keyguard showing state changed. */
void onKeyguardVisibilityChanged(boolean isShowing);
/** Returns {@link DividerView}. */
DividerView getDividerView();
/** Returns {@code true} if one of the split screen is in minimized mode. */
boolean isMinimized();
/** Returns {@code true} if the home stack is resizable. */
boolean isHomeStackResizable();
/** Returns {@code true} if the divider is visible. */
boolean isDividerVisible();
/** Switch to minimized state if appropriate. */
void setMinimized(boolean minimized);
/** Called when there's a task undocking. */
void onUndockingTask();
/** Called when app transition finished. */
void onAppTransitionFinished();
/** Dumps current status of Split Screen. */
void dump(PrintWriter pw);
/** Registers listener that gets called whenever the existence of the divider changes. */
void registerInSplitScreenListener(Consumer<Boolean> listener);
/** Unregisters listener that gets called whenever the existence of the divider changes. */
void unregisterInSplitScreenListener(Consumer<Boolean> listener);
/** Registers listener that gets called whenever the split screen bounds changes. */
void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener);
/** @return the container token for the secondary split root task. */
WindowContainerToken getSecondaryRoot();
/**
* Splits the primary task if feasible, this is to preserve legacy way to toggle split screen.
* Like triggering split screen through long pressing recents app button or through
* {@link android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN}.
*
* @return {@code true} if it successes to split the primary task.
*/
boolean splitPrimaryTask();
/**
* Exits the split to make the primary task fullscreen.
*/
void dismissSplitToPrimaryTask();
}

View File

@@ -1,762 +0,0 @@
/*
* 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.wm.shell.legacysplitscreen;
import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.Display.DEFAULT_DISPLAY;
import android.animation.AnimationHandler;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.RemoteException;
import android.provider.Settings;
import android.util.Slog;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Toast;
import android.window.TaskOrganizer;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
* Controls split screen feature.
*/
public class LegacySplitScreenController implements DisplayController.OnDisplaysChangedListener {
static final boolean DEBUG = false;
private static final String TAG = "SplitScreenCtrl";
private static final int DEFAULT_APP_TRANSITION_DURATION = 336;
private final Context mContext;
private final DisplayChangeController.OnDisplayChangingListener mRotationController;
private final DisplayController mDisplayController;
private final DisplayImeController mImeController;
private final DividerImeController mImePositionProcessor;
private final DividerState mDividerState = new DividerState();
private final ForcedResizableInfoActivityController mForcedResizableController;
private final ShellExecutor mMainExecutor;
private final AnimationHandler mSfVsyncAnimationHandler;
private final LegacySplitScreenTaskListener mSplits;
private final SystemWindows mSystemWindows;
final TransactionPool mTransactionPool;
private final WindowManagerProxy mWindowManagerProxy;
private final TaskOrganizer mTaskOrganizer;
private final SplitScreenImpl mImpl = new SplitScreenImpl();
private final CopyOnWriteArrayList<WeakReference<Consumer<Boolean>>> mDockedStackExistsListeners
= new CopyOnWriteArrayList<>();
private final ArrayList<WeakReference<BiConsumer<Rect, Rect>>> mBoundsChangedListeners =
new ArrayList<>();
private DividerWindowManager mWindowManager;
private DividerView mView;
// Keeps track of real-time split geometry including snap positions and ime adjustments
private LegacySplitDisplayLayout mSplitLayout;
// Transient: this contains the layout calculated for a new rotation requested by WM. This is
// kept around so that we can wait for a matching configuration change and then use the exact
// layout that we sent back to WM.
private LegacySplitDisplayLayout mRotateSplitLayout;
private boolean mIsKeyguardShowing;
private boolean mVisible = false;
private volatile boolean mMinimized = false;
private volatile boolean mAdjustedForIme = false;
private boolean mHomeStackResizable = false;
public LegacySplitScreenController(Context context,
DisplayController displayController, SystemWindows systemWindows,
DisplayImeController imeController, TransactionPool transactionPool,
ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
TaskStackListenerImpl taskStackListener, Transitions transitions,
ShellExecutor mainExecutor, AnimationHandler sfVsyncAnimationHandler) {
mContext = context;
mDisplayController = displayController;
mSystemWindows = systemWindows;
mImeController = imeController;
mMainExecutor = mainExecutor;
mSfVsyncAnimationHandler = sfVsyncAnimationHandler;
mForcedResizableController = new ForcedResizableInfoActivityController(context, this,
mainExecutor);
mTransactionPool = transactionPool;
mWindowManagerProxy = new WindowManagerProxy(syncQueue, shellTaskOrganizer);
mTaskOrganizer = shellTaskOrganizer;
mSplits = new LegacySplitScreenTaskListener(this, shellTaskOrganizer, transitions,
syncQueue);
mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mMainExecutor,
shellTaskOrganizer);
mRotationController =
(display, fromRotation, toRotation, wct) -> {
if (!mSplits.isSplitScreenSupported() || mWindowManagerProxy == null) {
return;
}
WindowContainerTransaction t = new WindowContainerTransaction();
DisplayLayout displayLayout =
new DisplayLayout(mDisplayController.getDisplayLayout(display));
LegacySplitDisplayLayout sdl =
new LegacySplitDisplayLayout(mContext, displayLayout, mSplits);
sdl.rotateTo(toRotation);
mRotateSplitLayout = sdl;
// snap resets to middle target when not minimized and rotation changed.
final int position = mMinimized ? mView.mSnapTargetBeforeMinimized.position
: sdl.getSnapAlgorithm().getMiddleTarget().position;
DividerSnapAlgorithm snap = sdl.getSnapAlgorithm();
final DividerSnapAlgorithm.SnapTarget target =
snap.calculateNonDismissingSnapTarget(position);
sdl.resizeSplits(target.position, t);
if (isSplitActive() && mHomeStackResizable) {
mWindowManagerProxy
.applyHomeTasksMinimized(sdl, mSplits.mSecondary.token, t);
}
if (mWindowManagerProxy.queueSyncTransactionIfWaiting(t)) {
// Because sync transactions are serialized, its possible for an "older"
// bounds-change to get applied after a screen rotation. In that case, we
// want to actually defer on that rather than apply immediately. Of course,
// this means that the bounds may not change until after the rotation so
// the user might see some artifacts. This should be rare.
Slog.w(TAG, "Screen rotated while other operations were pending, this may"
+ " result in some graphical artifacts.");
} else {
wct.merge(t, true /* transfer */);
}
};
mWindowManager = new DividerWindowManager(mSystemWindows);
// No need to listen to display window container or create root tasks if the device is not
// using legacy split screen.
if (!context.getResources().getBoolean(com.android.internal.R.bool.config_useLegacySplit)) {
return;
}
mDisplayController.addDisplayWindowListener(this);
// Don't initialize the divider or anything until we get the default display.
taskStackListener.addListener(
new TaskStackListenerCallback() {
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
if (!wasVisible || task.getWindowingMode()
!= WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
|| !mSplits.isSplitScreenSupported()) {
return;
}
if (isMinimized()) {
onUndockingTask();
}
}
@Override
public void onActivityForcedResizable(String packageName, int taskId,
int reason) {
mForcedResizableController.activityForcedResizable(packageName, taskId,
reason);
}
@Override
public void onActivityDismissingDockedStack() {
mForcedResizableController.activityDismissingSplitScreen();
}
@Override
public void onActivityLaunchOnSecondaryDisplayFailed() {
mForcedResizableController.activityLaunchOnSecondaryDisplayFailed();
}
});
}
public LegacySplitScreen asLegacySplitScreen() {
return mImpl;
}
public void onSplitScreenSupported() {
// Set starting tile bounds based on middle target
final WindowContainerTransaction tct = new WindowContainerTransaction();
int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
mSplitLayout.resizeSplits(midPos, tct);
mTaskOrganizer.applyTransaction(tct);
}
public void onKeyguardVisibilityChanged(boolean showing) {
if (!isSplitActive() || mView == null) {
return;
}
mView.setHidden(showing);
mIsKeyguardShowing = showing;
}
@Override
public void onDisplayAdded(int displayId) {
if (displayId != DEFAULT_DISPLAY) {
return;
}
mSplitLayout = new LegacySplitDisplayLayout(mDisplayController.getDisplayContext(displayId),
mDisplayController.getDisplayLayout(displayId), mSplits);
mImeController.addPositionProcessor(mImePositionProcessor);
mDisplayController.addDisplayChangingController(mRotationController);
if (!ActivityTaskManager.supportsSplitScreenMultiWindow(mContext)) {
removeDivider();
return;
}
try {
mSplits.init();
} catch (Exception e) {
Slog.e(TAG, "Failed to register docked stack listener", e);
removeDivider();
return;
}
}
@Override
public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
if (displayId != DEFAULT_DISPLAY || !mSplits.isSplitScreenSupported()) {
return;
}
mSplitLayout = new LegacySplitDisplayLayout(mDisplayController.getDisplayContext(displayId),
mDisplayController.getDisplayLayout(displayId), mSplits);
if (mRotateSplitLayout == null) {
int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
final WindowContainerTransaction tct = new WindowContainerTransaction();
mSplitLayout.resizeSplits(midPos, tct);
mTaskOrganizer.applyTransaction(tct);
} else if (mSplitLayout.mDisplayLayout.rotation()
== mRotateSplitLayout.mDisplayLayout.rotation()) {
mSplitLayout.mPrimary = new Rect(mRotateSplitLayout.mPrimary);
mSplitLayout.mSecondary = new Rect(mRotateSplitLayout.mSecondary);
mRotateSplitLayout = null;
}
if (isSplitActive()) {
update(newConfig);
}
}
public boolean isMinimized() {
return mMinimized;
}
public boolean isHomeStackResizable() {
return mHomeStackResizable;
}
public DividerView getDividerView() {
return mView;
}
public boolean isDividerVisible() {
return mView != null && mView.getVisibility() == View.VISIBLE;
}
/**
* This indicates that at-least one of the splits has content. This differs from
* isDividerVisible because the divider is only visible once *everything* is in split mode
* while this only cares if some things are (eg. while entering/exiting as well).
*/
public boolean isSplitActive() {
return mSplits.mPrimary != null && mSplits.mSecondary != null
&& (mSplits.mPrimary.topActivityType != ACTIVITY_TYPE_UNDEFINED
|| mSplits.mSecondary.topActivityType != ACTIVITY_TYPE_UNDEFINED);
}
public void addDivider(Configuration configuration) {
Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId());
mView = (DividerView)
LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null);
mView.setAnimationHandler(mSfVsyncAnimationHandler);
DisplayLayout displayLayout = mDisplayController.getDisplayLayout(mContext.getDisplayId());
mView.injectDependencies(this, mWindowManager, mDividerState, mForcedResizableController,
mSplits, mSplitLayout, mImePositionProcessor, mWindowManagerProxy);
mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
mView.setMinimizedDockStack(mMinimized, mHomeStackResizable, null /* transaction */);
final int size = dctx.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.docked_stack_divider_thickness);
final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
final int width = landscape ? size : displayLayout.width();
final int height = landscape ? displayLayout.height() : size;
mWindowManager.add(mView, width, height, mContext.getDisplayId());
}
public void removeDivider() {
if (mView != null) {
mView.onDividerRemoved();
}
mWindowManager.remove();
}
public void update(Configuration configuration) {
final boolean isDividerHidden = mView != null && mIsKeyguardShowing;
removeDivider();
addDivider(configuration);
if (mMinimized) {
mView.setMinimizedDockStack(true, mHomeStackResizable, null /* transaction */);
updateTouchable();
}
mView.setHidden(isDividerHidden);
}
public void onTaskVanished() {
removeDivider();
}
public void updateVisibility(final boolean visible) {
if (DEBUG) Slog.d(TAG, "Updating visibility " + mVisible + "->" + visible);
if (mVisible != visible) {
mVisible = visible;
mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
if (visible) {
mView.enterSplitMode(mHomeStackResizable);
// Update state because animations won't finish.
mWindowManagerProxy.runInSync(
t -> mView.setMinimizedDockStack(mMinimized, mHomeStackResizable, t));
} else {
mView.exitSplitMode();
mWindowManagerProxy.runInSync(
t -> mView.setMinimizedDockStack(false, mHomeStackResizable, t));
}
// Notify existence listeners
synchronized (mDockedStackExistsListeners) {
mDockedStackExistsListeners.removeIf(wf -> {
Consumer<Boolean> l = wf.get();
if (l != null) l.accept(visible);
return l == null;
});
}
}
}
public void setMinimized(final boolean minimized) {
if (DEBUG) Slog.d(TAG, "posting ext setMinimized " + minimized + " vis:" + mVisible);
mMainExecutor.execute(() -> {
if (DEBUG) Slog.d(TAG, "run posted ext setMinimized " + minimized + " vis:" + mVisible);
if (!mVisible) {
return;
}
setHomeMinimized(minimized);
});
}
public void setHomeMinimized(final boolean minimized) {
if (DEBUG) {
Slog.d(TAG, "setHomeMinimized min:" + mMinimized + "->" + minimized + " hrsz:"
+ mHomeStackResizable + " split:" + isDividerVisible());
}
WindowContainerTransaction wct = new WindowContainerTransaction();
final boolean minimizedChanged = mMinimized != minimized;
// Update minimized state
if (minimizedChanged) {
mMinimized = minimized;
}
// Always set this because we could be entering split when mMinimized is already true
wct.setFocusable(mSplits.mPrimary.token, !mMinimized);
// Sync state to DividerView if it exists.
if (mView != null) {
final int displayId = mView.getDisplay() != null
? mView.getDisplay().getDisplayId() : DEFAULT_DISPLAY;
// pause ime here (before updateMinimizedDockedStack)
if (mMinimized) {
mImePositionProcessor.pause(displayId);
}
if (minimizedChanged) {
// This conflicts with IME adjustment, so only call it when things change.
mView.setMinimizedDockStack(minimized, getAnimDuration(), mHomeStackResizable);
}
if (!mMinimized) {
// afterwards so it can end any animations started in view
mImePositionProcessor.resume(displayId);
}
}
updateTouchable();
// If we are only setting focusability, a sync transaction isn't necessary (in fact it
// can interrupt other animations), so see if it can be submitted on pending instead.
if (!mWindowManagerProxy.queueSyncTransactionIfWaiting(wct)) {
mTaskOrganizer.applyTransaction(wct);
}
}
public void setAdjustedForIme(boolean adjustedForIme) {
if (mAdjustedForIme == adjustedForIme) {
return;
}
mAdjustedForIme = adjustedForIme;
updateTouchable();
}
public void updateTouchable() {
mWindowManager.setTouchable(!mAdjustedForIme);
}
public void onUndockingTask() {
if (mView != null) {
mView.onUndockingTask();
}
}
public void onAppTransitionFinished() {
if (mView == null) {
return;
}
mForcedResizableController.onAppTransitionFinished();
}
public void dump(PrintWriter pw) {
pw.print(" mVisible="); pw.println(mVisible);
pw.print(" mMinimized="); pw.println(mMinimized);
pw.print(" mAdjustedForIme="); pw.println(mAdjustedForIme);
}
public long getAnimDuration() {
float transitionScale = Settings.Global.getFloat(mContext.getContentResolver(),
Settings.Global.TRANSITION_ANIMATION_SCALE,
mContext.getResources().getFloat(
com.android.internal.R.dimen
.config_appTransitionAnimationDurationScaleDefault));
final long transitionDuration = DEFAULT_APP_TRANSITION_DURATION;
return (long) (transitionDuration * transitionScale);
}
public void registerInSplitScreenListener(Consumer<Boolean> listener) {
listener.accept(isDividerVisible());
synchronized (mDockedStackExistsListeners) {
mDockedStackExistsListeners.add(new WeakReference<>(listener));
}
}
public void unregisterInSplitScreenListener(Consumer<Boolean> listener) {
synchronized (mDockedStackExistsListeners) {
for (int i = mDockedStackExistsListeners.size() - 1; i >= 0; i--) {
if (mDockedStackExistsListeners.get(i) == listener) {
mDockedStackExistsListeners.remove(i);
}
}
}
}
public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
synchronized (mBoundsChangedListeners) {
mBoundsChangedListeners.add(new WeakReference<>(listener));
}
}
public boolean splitPrimaryTask() {
try {
if (ActivityTaskManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_PINNED) {
return false;
}
} catch (RemoteException e) {
return false;
}
if (isSplitActive() || mSplits.mPrimary == null) {
return false;
}
// Try fetching the top running task.
final List<RunningTaskInfo> runningTasks =
ActivityTaskManager.getInstance().getTasks(1 /* maxNum */);
if (runningTasks == null || runningTasks.isEmpty()) {
return false;
}
// Note: The set of running tasks from the system is ordered by recency.
final RunningTaskInfo topRunningTask = runningTasks.get(0);
final int activityType = topRunningTask.getActivityType();
if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
return false;
}
if (!topRunningTask.supportsSplitScreenMultiWindow) {
Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text,
Toast.LENGTH_SHORT).show();
return false;
}
final WindowContainerTransaction wct = new WindowContainerTransaction();
// Clear out current windowing mode before reparenting to split task.
wct.setWindowingMode(topRunningTask.token, WINDOWING_MODE_UNDEFINED);
wct.reparent(topRunningTask.token, mSplits.mPrimary.token, true /* onTop */);
mWindowManagerProxy.applySyncTransaction(wct);
return true;
}
public void dismissSplitToPrimaryTask() {
startDismissSplit(true /* toPrimaryTask */);
}
/** Notifies the bounds of split screen changed. */
public void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
synchronized (mBoundsChangedListeners) {
mBoundsChangedListeners.removeIf(wf -> {
BiConsumer<Rect, Rect> l = wf.get();
if (l != null) l.accept(secondaryWindowBounds, secondaryWindowInsets);
return l == null;
});
}
}
public void startEnterSplit() {
update(mDisplayController.getDisplayContext(
mContext.getDisplayId()).getResources().getConfiguration());
// Set resizable directly here because applyEnterSplit already resizes home stack.
mHomeStackResizable = mWindowManagerProxy.applyEnterSplit(mSplits,
mRotateSplitLayout != null ? mRotateSplitLayout : mSplitLayout);
}
public void prepareEnterSplitTransition(WindowContainerTransaction outWct) {
// Set resizable directly here because buildEnterSplit already resizes home stack.
mHomeStackResizable = mWindowManagerProxy.buildEnterSplit(outWct, mSplits,
mRotateSplitLayout != null ? mRotateSplitLayout : mSplitLayout);
}
public void finishEnterSplitTransition(boolean minimized) {
update(mDisplayController.getDisplayContext(
mContext.getDisplayId()).getResources().getConfiguration());
if (minimized) {
ensureMinimizedSplit();
} else {
ensureNormalSplit();
}
}
public void startDismissSplit(boolean toPrimaryTask) {
startDismissSplit(toPrimaryTask, false /* snapped */);
}
public void startDismissSplit(boolean toPrimaryTask, boolean snapped) {
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
mSplits.getSplitTransitions().dismissSplit(
mSplits, mSplitLayout, !toPrimaryTask, snapped);
} else {
mWindowManagerProxy.applyDismissSplit(mSplits, mSplitLayout, !toPrimaryTask);
onDismissSplit();
}
}
public void onDismissSplit() {
updateVisibility(false /* visible */);
mMinimized = false;
// Resets divider bar position to undefined, so new divider bar will apply default position
// next time entering split mode.
mDividerState.mRatioPositionBeforeMinimized = 0;
removeDivider();
mImePositionProcessor.reset();
}
public void ensureMinimizedSplit() {
setHomeMinimized(true /* minimized */);
if (mView != null && !isDividerVisible()) {
// Wasn't in split-mode yet, so enter now.
if (DEBUG) {
Slog.d(TAG, " entering split mode with minimized=true");
}
updateVisibility(true /* visible */);
}
}
public void ensureNormalSplit() {
setHomeMinimized(false /* minimized */);
if (mView != null && !isDividerVisible()) {
// Wasn't in split-mode, so enter now.
if (DEBUG) {
Slog.d(TAG, " enter split mode unminimized ");
}
updateVisibility(true /* visible */);
}
}
public LegacySplitDisplayLayout getSplitLayout() {
return mSplitLayout;
}
public WindowManagerProxy getWmProxy() {
return mWindowManagerProxy;
}
public WindowContainerToken getSecondaryRoot() {
if (mSplits == null || mSplits.mSecondary == null) {
return null;
}
return mSplits.mSecondary.token;
}
private class SplitScreenImpl implements LegacySplitScreen {
@Override
public boolean isMinimized() {
return mMinimized;
}
@Override
public boolean isHomeStackResizable() {
return mHomeStackResizable;
}
/**
* TODO: Remove usage from outside the shell.
*/
@Override
public DividerView getDividerView() {
return LegacySplitScreenController.this.getDividerView();
}
@Override
public boolean isDividerVisible() {
boolean[] result = new boolean[1];
try {
mMainExecutor.executeBlocking(() -> {
result[0] = LegacySplitScreenController.this.isDividerVisible();
});
} catch (InterruptedException e) {
Slog.e(TAG, "Failed to get divider visible");
}
return result[0];
}
@Override
public void onKeyguardVisibilityChanged(boolean isShowing) {
mMainExecutor.execute(() -> {
LegacySplitScreenController.this.onKeyguardVisibilityChanged(isShowing);
});
}
@Override
public void setMinimized(boolean minimized) {
mMainExecutor.execute(() -> {
LegacySplitScreenController.this.setMinimized(minimized);
});
}
@Override
public void onUndockingTask() {
mMainExecutor.execute(() -> {
LegacySplitScreenController.this.onUndockingTask();
});
}
@Override
public void onAppTransitionFinished() {
mMainExecutor.execute(() -> {
LegacySplitScreenController.this.onAppTransitionFinished();
});
}
@Override
public void registerInSplitScreenListener(Consumer<Boolean> listener) {
mMainExecutor.execute(() -> {
LegacySplitScreenController.this.registerInSplitScreenListener(listener);
});
}
@Override
public void unregisterInSplitScreenListener(Consumer<Boolean> listener) {
mMainExecutor.execute(() -> {
LegacySplitScreenController.this.unregisterInSplitScreenListener(listener);
});
}
@Override
public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
mMainExecutor.execute(() -> {
LegacySplitScreenController.this.registerBoundsChangeListener(listener);
});
}
@Override
public WindowContainerToken getSecondaryRoot() {
WindowContainerToken[] result = new WindowContainerToken[1];
try {
mMainExecutor.executeBlocking(() -> {
result[0] = LegacySplitScreenController.this.getSecondaryRoot();
});
} catch (InterruptedException e) {
Slog.e(TAG, "Failed to get secondary root");
}
return result[0];
}
@Override
public boolean splitPrimaryTask() {
boolean[] result = new boolean[1];
try {
mMainExecutor.executeBlocking(() -> {
result[0] = LegacySplitScreenController.this.splitPrimaryTask();
});
} catch (InterruptedException e) {
Slog.e(TAG, "Failed to split primary task");
}
return result[0];
}
@Override
public void dismissSplitToPrimaryTask() {
mMainExecutor.execute(() -> {
LegacySplitScreenController.this.dismissSplitToPrimaryTask();
});
}
@Override
public void dump(PrintWriter pw) {
try {
mMainExecutor.executeBlocking(() -> {
LegacySplitScreenController.this.dump(pw);
});
} catch (InterruptedException e) {
Slog.e(TAG, "Failed to dump LegacySplitScreenController in 2s");
}
}
}
}

View File

@@ -1,376 +0,0 @@
/*
* 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.wm.shell.legacysplitscreen;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import android.app.ActivityManager.RunningTaskInfo;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.Log;
import android.util.SparseArray;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.TaskOrganizer;
import androidx.annotation.NonNull;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SurfaceUtils;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.ArrayList;
class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener {
private static final String TAG = LegacySplitScreenTaskListener.class.getSimpleName();
private static final boolean DEBUG = LegacySplitScreenController.DEBUG;
private final ShellTaskOrganizer mTaskOrganizer;
private final SyncTransactionQueue mSyncQueue;
private final SparseArray<SurfaceControl> mLeashByTaskId = new SparseArray<>();
// TODO(shell-transitions): Remove when switched to shell-transitions.
private final SparseArray<Point> mPositionByTaskId = new SparseArray<>();
RunningTaskInfo mPrimary;
RunningTaskInfo mSecondary;
SurfaceControl mPrimarySurface;
SurfaceControl mSecondarySurface;
SurfaceControl mPrimaryDim;
SurfaceControl mSecondaryDim;
Rect mHomeBounds = new Rect();
final LegacySplitScreenController mSplitScreenController;
private boolean mSplitScreenSupported = false;
final SurfaceSession mSurfaceSession = new SurfaceSession();
private final LegacySplitScreenTransitions mSplitTransitions;
LegacySplitScreenTaskListener(LegacySplitScreenController splitScreenController,
ShellTaskOrganizer shellTaskOrganizer,
Transitions transitions,
SyncTransactionQueue syncQueue) {
mSplitScreenController = splitScreenController;
mTaskOrganizer = shellTaskOrganizer;
mSplitTransitions = new LegacySplitScreenTransitions(splitScreenController.mTransactionPool,
transitions, mSplitScreenController, this);
transitions.addHandler(mSplitTransitions);
mSyncQueue = syncQueue;
}
void init() {
synchronized (this) {
try {
mTaskOrganizer.createRootTask(
DEFAULT_DISPLAY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, this);
mTaskOrganizer.createRootTask(
DEFAULT_DISPLAY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, this);
} catch (Exception e) {
// teardown to prevent callbacks
mTaskOrganizer.removeListener(this);
throw e;
}
}
}
boolean isSplitScreenSupported() {
return mSplitScreenSupported;
}
SurfaceControl.Transaction getTransaction() {
return mSplitScreenController.mTransactionPool.acquire();
}
void releaseTransaction(SurfaceControl.Transaction t) {
mSplitScreenController.mTransactionPool.release(t);
}
TaskOrganizer getTaskOrganizer() {
return mTaskOrganizer;
}
LegacySplitScreenTransitions getSplitTransitions() {
return mSplitTransitions;
}
@Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
synchronized (this) {
if (taskInfo.hasParentTask()) {
handleChildTaskAppeared(taskInfo, leash);
return;
}
final int winMode = taskInfo.getWindowingMode();
if (winMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
ProtoLog.v(WM_SHELL_TASK_ORG,
"%s onTaskAppeared Primary taskId=%d", TAG, taskInfo.taskId);
mPrimary = taskInfo;
mPrimarySurface = leash;
} else if (winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
ProtoLog.v(WM_SHELL_TASK_ORG,
"%s onTaskAppeared Secondary taskId=%d", TAG, taskInfo.taskId);
mSecondary = taskInfo;
mSecondarySurface = leash;
} else {
ProtoLog.v(WM_SHELL_TASK_ORG, "%s onTaskAppeared unknown taskId=%d winMode=%d",
TAG, taskInfo.taskId, winMode);
}
if (!mSplitScreenSupported && mPrimarySurface != null && mSecondarySurface != null) {
mSplitScreenSupported = true;
mSplitScreenController.onSplitScreenSupported();
ProtoLog.v(WM_SHELL_TASK_ORG, "%s onTaskAppeared Supported", TAG);
// Initialize dim surfaces:
SurfaceControl.Transaction t = getTransaction();
mPrimaryDim = SurfaceUtils.makeDimLayer(
t, mPrimarySurface, "Primary Divider Dim", mSurfaceSession);
mSecondaryDim = SurfaceUtils.makeDimLayer(
t, mSecondarySurface, "Secondary Divider Dim", mSurfaceSession);
t.apply();
releaseTransaction(t);
}
}
}
@Override
public void onTaskVanished(RunningTaskInfo taskInfo) {
synchronized (this) {
mPositionByTaskId.remove(taskInfo.taskId);
if (taskInfo.hasParentTask()) {
mLeashByTaskId.remove(taskInfo.taskId);
return;
}
final boolean isPrimaryTask = mPrimary != null
&& taskInfo.token.equals(mPrimary.token);
final boolean isSecondaryTask = mSecondary != null
&& taskInfo.token.equals(mSecondary.token);
if (mSplitScreenSupported && (isPrimaryTask || isSecondaryTask)) {
mSplitScreenSupported = false;
SurfaceControl.Transaction t = getTransaction();
t.remove(mPrimaryDim);
t.remove(mSecondaryDim);
t.remove(mPrimarySurface);
t.remove(mSecondarySurface);
t.apply();
releaseTransaction(t);
mSplitScreenController.onTaskVanished();
}
}
}
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
if (taskInfo.displayId != DEFAULT_DISPLAY) {
return;
}
synchronized (this) {
if (!taskInfo.supportsMultiWindow) {
if (mSplitScreenController.isDividerVisible()) {
// Dismiss the split screen if the task no longer supports multi window.
if (taskInfo.taskId == mPrimary.taskId
|| taskInfo.parentTaskId == mPrimary.taskId) {
// If the primary is focused, dismiss to primary.
mSplitScreenController
.startDismissSplit(taskInfo.isFocused /* toPrimaryTask */);
} else {
// If the secondary is not focused, dismiss to primary.
mSplitScreenController
.startDismissSplit(!taskInfo.isFocused /* toPrimaryTask */);
}
}
return;
}
if (taskInfo.hasParentTask()) {
// changed messages are noisy since it reports on every ensureVisibility. This
// conflicts with legacy app-transitions which "swaps" the position to a
// leash. For now, only update when position actually changes to avoid
// poorly-timed duplicate calls.
if (taskInfo.positionInParent.equals(mPositionByTaskId.get(taskInfo.taskId))) {
return;
}
handleChildTaskChanged(taskInfo);
} else {
handleTaskInfoChanged(taskInfo);
}
mPositionByTaskId.put(taskInfo.taskId, new Point(taskInfo.positionInParent));
}
}
private void handleChildTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
mLeashByTaskId.put(taskInfo.taskId, leash);
mPositionByTaskId.put(taskInfo.taskId, new Point(taskInfo.positionInParent));
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
}
private void handleChildTaskChanged(RunningTaskInfo taskInfo) {
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId);
updateChildTaskSurface(taskInfo, leash, false /* firstAppeared */);
}
private void updateChildTaskSurface(
RunningTaskInfo taskInfo, SurfaceControl leash, boolean firstAppeared) {
final Point taskPositionInParent = taskInfo.positionInParent;
mSyncQueue.runInSync(t -> {
t.setWindowCrop(leash, null);
t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);
if (firstAppeared && !Transitions.ENABLE_SHELL_TRANSITIONS) {
t.setAlpha(leash, 1f);
t.setMatrix(leash, 1, 0, 0, 1);
t.show(leash);
}
});
}
/**
* This is effectively a finite state machine which moves between the various split-screen
* presentations based on the contents of the split regions.
*/
private void handleTaskInfoChanged(RunningTaskInfo info) {
if (!mSplitScreenSupported) {
// This shouldn't happen; but apparently there is a chance that SysUI crashes without
// system server receiving binder-death (or maybe it receives binder-death too late?).
// In this situation, when sys-ui restarts, the split root-tasks will still exist so
// there is a small window of time during init() where WM might send messages here
// before init() fails. So, avoid a cycle of crashes by returning early.
Log.e(TAG, "Got handleTaskInfoChanged when not initialized: " + info);
return;
}
final boolean secondaryImpliedMinimize = mSecondary.topActivityType == ACTIVITY_TYPE_HOME
|| (mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS
&& mSplitScreenController.isHomeStackResizable());
final boolean primaryWasEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
final boolean secondaryWasEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
if (info.token.asBinder() == mPrimary.token.asBinder()) {
mPrimary = info;
} else if (info.token.asBinder() == mSecondary.token.asBinder()) {
mSecondary = info;
}
if (DEBUG) {
Log.d(TAG, "onTaskInfoChanged " + mPrimary + " " + mSecondary);
}
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
final boolean primaryIsEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
final boolean secondaryIsEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
final boolean secondaryImpliesMinimize = mSecondary.topActivityType == ACTIVITY_TYPE_HOME
|| (mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS
&& mSplitScreenController.isHomeStackResizable());
if (primaryIsEmpty == primaryWasEmpty && secondaryWasEmpty == secondaryIsEmpty
&& secondaryImpliedMinimize == secondaryImpliesMinimize) {
// No relevant changes
return;
}
if (primaryIsEmpty || secondaryIsEmpty) {
// At-least one of the splits is empty which means we are currently transitioning
// into or out-of split-screen mode.
if (DEBUG) {
Log.d(TAG, " at-least one split empty " + mPrimary.topActivityType
+ " " + mSecondary.topActivityType);
}
if (mSplitScreenController.isDividerVisible()) {
// Was in split-mode, which means we are leaving split, so continue that.
// This happens when the stack in the primary-split is dismissed.
if (DEBUG) {
Log.d(TAG, " was in split, so this means leave it "
+ mPrimary.topActivityType + " " + mSecondary.topActivityType);
}
mSplitScreenController.startDismissSplit(false /* toPrimaryTask */);
} else if (!primaryIsEmpty && primaryWasEmpty && secondaryWasEmpty) {
// Wasn't in split-mode (both were empty), but now that the primary split is
// populated, we should fully enter split by moving everything else into secondary.
// This just tells window-manager to reparent things, the UI will respond
// when it gets new task info for the secondary split.
if (DEBUG) {
Log.d(TAG, " was not in split, but primary is populated, so enter it");
}
mSplitScreenController.startEnterSplit();
}
} else if (secondaryImpliesMinimize) {
// Workaround for b/172686383, we can't rely on the sync bounds change transaction for
// the home task to finish before the last updateChildTaskSurface() call even if it's
// queued on the sync transaction queue, so ensure that the home task surface is updated
// again before we minimize
final ArrayList<RunningTaskInfo> tasks = new ArrayList<>();
mSplitScreenController.getWmProxy().getHomeAndRecentsTasks(tasks,
mSplitScreenController.getSecondaryRoot());
for (int i = 0; i < tasks.size(); i++) {
final RunningTaskInfo taskInfo = tasks.get(i);
final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId);
if (leash != null) {
updateChildTaskSurface(taskInfo, leash, false /* firstAppeared */);
}
}
// Both splits are populated but the secondary split has a home/recents stack on top,
// so enter minimized mode.
mSplitScreenController.ensureMinimizedSplit();
} else {
// Both splits are populated by normal activities, so make sure we aren't minimized.
mSplitScreenController.ensureNormalSplit();
}
}
@Override
public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
b.setParent(findTaskSurface(taskId));
}
@Override
public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
SurfaceControl.Transaction t) {
t.reparent(sc, findTaskSurface(taskId));
}
private SurfaceControl findTaskSurface(int taskId) {
if (!mLeashByTaskId.contains(taskId)) {
throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
}
return mLeashByTaskId.get(taskId);
}
@Override
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " ";
pw.println(prefix + this);
pw.println(innerPrefix + "mSplitScreenSupported=" + mSplitScreenSupported);
if (mPrimary != null) pw.println(innerPrefix + "mPrimary.taskId=" + mPrimary.taskId);
if (mSecondary != null) pw.println(innerPrefix + "mSecondary.taskId=" + mSecondary.taskId);
}
@Override
public String toString() {
return TAG;
}
}

View File

@@ -1,348 +0,0 @@
/*
* 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.wm.shell.legacysplitscreen;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
/** Plays transition animations for split-screen */
public class LegacySplitScreenTransitions implements Transitions.TransitionHandler {
private static final String TAG = "SplitScreenTransitions";
public static final int TRANSIT_SPLIT_DISMISS_SNAP = TRANSIT_FIRST_CUSTOM + 10;
private final TransactionPool mTransactionPool;
private final Transitions mTransitions;
private final LegacySplitScreenController mSplitScreen;
private final LegacySplitScreenTaskListener mListener;
private IBinder mPendingDismiss = null;
private boolean mDismissFromSnap = false;
private IBinder mPendingEnter = null;
private IBinder mAnimatingTransition = null;
/** Keeps track of currently running animations */
private final ArrayList<Animator> mAnimations = new ArrayList<>();
private Transitions.TransitionFinishCallback mFinishCallback = null;
private SurfaceControl.Transaction mFinishTransaction;
LegacySplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
@NonNull LegacySplitScreenController splitScreen,
@NonNull LegacySplitScreenTaskListener listener) {
mTransactionPool = pool;
mTransitions = transitions;
mSplitScreen = splitScreen;
mListener = listener;
}
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@Nullable TransitionRequestInfo request) {
WindowContainerTransaction out = null;
final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
final @WindowManager.TransitionType int type = request.getType();
if (mSplitScreen.isDividerVisible()) {
// try to handle everything while in split-screen
out = new WindowContainerTransaction();
if (triggerTask != null) {
final boolean shouldDismiss =
// if we close the primary-docked task, then leave split-screen since there
// is nothing behind it.
((type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK)
&& triggerTask.parentTaskId == mListener.mPrimary.taskId)
// if an activity that is not supported in multi window mode is launched,
// we also need to leave split-screen.
|| ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)
&& !triggerTask.supportsMultiWindow);
// In both cases, dismiss the primary
if (shouldDismiss) {
WindowManagerProxy.buildDismissSplit(out, mListener,
mSplitScreen.getSplitLayout(), true /* dismiss */);
if (type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT) {
out.reorder(triggerTask.token, true /* onTop */);
}
mPendingDismiss = transition;
}
}
} else if (triggerTask != null) {
// Not in split mode, so look for an open with a trigger task.
if ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)
&& triggerTask.configuration.windowConfiguration.getWindowingMode()
== WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
out = new WindowContainerTransaction();
mSplitScreen.prepareEnterSplitTransition(out);
mPendingEnter = transition;
}
}
return out;
}
// TODO(shell-transitions): real animations
private void startExampleAnimation(@NonNull SurfaceControl leash, boolean show) {
final float end = show ? 1.f : 0.f;
final float start = 1.f - end;
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
final ValueAnimator va = ValueAnimator.ofFloat(start, end);
va.setDuration(500);
va.addUpdateListener(animation -> {
float fraction = animation.getAnimatedFraction();
transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
transaction.apply();
});
final Runnable finisher = () -> {
transaction.setAlpha(leash, end);
transaction.apply();
mTransactionPool.release(transaction);
mTransitions.getMainExecutor().execute(() -> {
mAnimations.remove(va);
onFinish();
});
};
va.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) { }
@Override
public void onAnimationEnd(Animator animation) {
finisher.run();
}
@Override
public void onAnimationCancel(Animator animation) {
finisher.run();
}
@Override
public void onAnimationRepeat(Animator animation) { }
});
mAnimations.add(va);
mTransitions.getAnimExecutor().execute(va::start);
}
// TODO(shell-transitions): real animations
private void startExampleResizeAnimation(@NonNull SurfaceControl leash,
@NonNull Rect startBounds, @NonNull Rect endBounds) {
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f);
va.setDuration(500);
va.addUpdateListener(animation -> {
float fraction = animation.getAnimatedFraction();
transaction.setWindowCrop(leash,
(int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction),
(int) (startBounds.height() * (1.f - fraction)
+ endBounds.height() * fraction));
transaction.setPosition(leash,
startBounds.left * (1.f - fraction) + endBounds.left * fraction,
startBounds.top * (1.f - fraction) + endBounds.top * fraction);
transaction.apply();
});
final Runnable finisher = () -> {
transaction.setWindowCrop(leash, 0, 0);
transaction.setPosition(leash, endBounds.left, endBounds.top);
transaction.apply();
mTransactionPool.release(transaction);
mTransitions.getMainExecutor().execute(() -> {
mAnimations.remove(va);
onFinish();
});
};
va.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
finisher.run();
}
@Override
public void onAnimationCancel(Animator animation) {
finisher.run();
}
});
mAnimations.add(va);
mTransitions.getAnimExecutor().execute(va::start);
}
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (transition != mPendingDismiss && transition != mPendingEnter) {
// If we're not in split-mode, just abort
if (!mSplitScreen.isDividerVisible()) return false;
// Check to see if HOME is involved
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
if (change.getTaskInfo() == null
|| change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME) continue;
if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) {
mSplitScreen.ensureMinimizedSplit();
} else if (change.getMode() == TRANSIT_CLOSE
|| change.getMode() == TRANSIT_TO_BACK) {
mSplitScreen.ensureNormalSplit();
}
}
// Use normal animations.
return false;
}
mFinishCallback = finishCallback;
mFinishTransaction = mTransactionPool.acquire();
mAnimatingTransition = transition;
// Play fade animations
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
final SurfaceControl leash = change.getLeash();
final int mode = info.getChanges().get(i).getMode();
if (mode == TRANSIT_CHANGE) {
if (change.getParent() != null) {
// This is probably reparented, so we want the parent to be immediately visible
final TransitionInfo.Change parentChange = info.getChange(change.getParent());
startTransaction.show(parentChange.getLeash());
startTransaction.setAlpha(parentChange.getLeash(), 1.f);
// and then animate this layer outside the parent (since, for example, this is
// the home task animating from fullscreen to part-screen).
startTransaction.reparent(leash, info.getRootLeash());
startTransaction.setLayer(leash, info.getChanges().size() - i);
// build the finish reparent/reposition
mFinishTransaction.reparent(leash, parentChange.getLeash());
mFinishTransaction.setPosition(leash,
change.getEndRelOffset().x, change.getEndRelOffset().y);
}
// TODO(shell-transitions): screenshot here
final Rect startBounds = new Rect(change.getStartAbsBounds());
final boolean isHome = change.getTaskInfo() != null
&& change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME;
if (mPendingDismiss == transition && mDismissFromSnap && !isHome) {
// Home is special since it doesn't move during fling. Everything else, though,
// when dismissing from snap, the top/left is at 0,0.
startBounds.offsetTo(0, 0);
}
final Rect endBounds = new Rect(change.getEndAbsBounds());
startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
startExampleResizeAnimation(leash, startBounds, endBounds);
}
if (change.getParent() != null) {
continue;
}
if (transition == mPendingEnter
&& mListener.mPrimary.token.equals(change.getContainer())
|| mListener.mSecondary.token.equals(change.getContainer())) {
startTransaction.setWindowCrop(leash, change.getStartAbsBounds().width(),
change.getStartAbsBounds().height());
if (mListener.mPrimary.token.equals(change.getContainer())) {
// Move layer to top since we want it above the oversized home task during
// animation even though home task is on top in hierarchy.
startTransaction.setLayer(leash, info.getChanges().size() + 1);
}
}
boolean isOpening = Transitions.isOpeningType(info.getType());
if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
// fade in
startExampleAnimation(leash, true /* show */);
} else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
// fade out
if (transition == mPendingDismiss && mDismissFromSnap) {
// Dismissing via snap-to-top/bottom means that the dismissed task is already
// not-visible (usually cropped to oblivion) so immediately set its alpha to 0
// and don't animate it so it doesn't pop-in when reparented.
startTransaction.setAlpha(leash, 0.f);
} else {
startExampleAnimation(leash, false /* show */);
}
}
}
if (transition == mPendingEnter) {
// If entering, check if we should enter into minimized or normal split
boolean homeIsVisible = false;
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
if (change.getTaskInfo() == null
|| change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME) {
continue;
}
homeIsVisible = change.getMode() == TRANSIT_OPEN
|| change.getMode() == TRANSIT_TO_FRONT
|| change.getMode() == TRANSIT_CHANGE;
break;
}
mSplitScreen.finishEnterSplitTransition(homeIsVisible);
}
startTransaction.apply();
onFinish();
return true;
}
@ExternalThread
void dismissSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout,
boolean dismissOrMaximize, boolean snapped) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
WindowManagerProxy.buildDismissSplit(wct, tiles, layout, dismissOrMaximize);
mTransitions.getMainExecutor().execute(() -> {
mDismissFromSnap = snapped;
mPendingDismiss = mTransitions.startTransition(TRANSIT_SPLIT_DISMISS_SNAP, wct, this);
});
}
private void onFinish() {
if (!mAnimations.isEmpty()) return;
mFinishTransaction.apply();
mTransactionPool.release(mFinishTransaction);
mFinishTransaction = null;
mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
mFinishCallback = null;
if (mAnimatingTransition == mPendingEnter) {
mPendingEnter = null;
}
if (mAnimatingTransition == mPendingDismiss) {
mSplitScreen.onDismissSplit();
mPendingDismiss = null;
}
mDismissFromSnap = false;
mAnimatingTransition = null;
}
}

View File

@@ -1,99 +0,0 @@
/*
* Copyright (C) 2016 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.legacysplitscreen;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowManager;
import com.android.wm.shell.R;
/**
* Shadow for the minimized dock state on homescreen.
*/
public class MinimizedDockShadow extends View {
private final Paint mShadowPaint = new Paint();
private int mDockSide = WindowManager.DOCKED_INVALID;
public MinimizedDockShadow(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
void setDockSide(int dockSide) {
if (dockSide != mDockSide) {
mDockSide = dockSide;
updatePaint(getLeft(), getTop(), getRight(), getBottom());
invalidate();
}
}
private void updatePaint(int left, int top, int right, int bottom) {
int startColor = mContext.getResources().getColor(
R.color.minimize_dock_shadow_start, null);
int endColor = mContext.getResources().getColor(
R.color.minimize_dock_shadow_end, null);
final int middleColor = Color.argb(
(Color.alpha(startColor) + Color.alpha(endColor)) / 2, 0, 0, 0);
final int quarter = Color.argb(
(int) (Color.alpha(startColor) * 0.25f + Color.alpha(endColor) * 0.75f),
0, 0, 0);
if (mDockSide == WindowManager.DOCKED_TOP) {
mShadowPaint.setShader(new LinearGradient(
0, 0, 0, bottom - top,
new int[] { startColor, middleColor, quarter, endColor },
new float[] { 0f, 0.35f, 0.6f, 1f }, Shader.TileMode.CLAMP));
} else if (mDockSide == WindowManager.DOCKED_LEFT) {
mShadowPaint.setShader(new LinearGradient(
0, 0, right - left, 0,
new int[] { startColor, middleColor, quarter, endColor },
new float[] { 0f, 0.35f, 0.6f, 1f }, Shader.TileMode.CLAMP));
} else if (mDockSide == WindowManager.DOCKED_RIGHT) {
mShadowPaint.setShader(new LinearGradient(
right - left, 0, 0, 0,
new int[] { startColor, middleColor, quarter, endColor },
new float[] { 0f, 0.35f, 0.6f, 1f }, Shader.TileMode.CLAMP));
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
updatePaint(left, top, right, bottom);
invalidate();
}
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawRect(0, 0, getWidth(), getHeight(), mShadowPaint);
}
@Override
public boolean hasOverlappingRendering() {
return false;
}
}

View File

@@ -1,386 +0,0 @@
/*
* Copyright (C) 2015 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.legacysplitscreen;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.graphics.Rect;
import android.os.RemoteException;
import android.util.Log;
import android.view.Display;
import android.view.SurfaceControl;
import android.view.WindowManagerGlobal;
import android.window.TaskOrganizer;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
import java.util.List;
/**
* Proxy to simplify calls into window manager/activity manager
*/
class WindowManagerProxy {
private static final String TAG = "WindowManagerProxy";
private static final int[] HOME_AND_RECENTS = {ACTIVITY_TYPE_HOME, ACTIVITY_TYPE_RECENTS};
private static final int[] CONTROLLED_ACTIVITY_TYPES = {
ACTIVITY_TYPE_STANDARD,
ACTIVITY_TYPE_HOME,
ACTIVITY_TYPE_RECENTS,
ACTIVITY_TYPE_UNDEFINED
};
private static final int[] CONTROLLED_WINDOWING_MODES = {
WINDOWING_MODE_FULLSCREEN,
WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
WINDOWING_MODE_UNDEFINED
};
@GuardedBy("mDockedRect")
private final Rect mDockedRect = new Rect();
private final Rect mTmpRect1 = new Rect();
@GuardedBy("mDockedRect")
private final Rect mTouchableRegion = new Rect();
private final SyncTransactionQueue mSyncTransactionQueue;
private final TaskOrganizer mTaskOrganizer;
WindowManagerProxy(SyncTransactionQueue syncQueue, TaskOrganizer taskOrganizer) {
mSyncTransactionQueue = syncQueue;
mTaskOrganizer = taskOrganizer;
}
void dismissOrMaximizeDocked(final LegacySplitScreenTaskListener tiles,
LegacySplitDisplayLayout layout, final boolean dismissOrMaximize) {
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
tiles.mSplitScreenController.startDismissSplit(!dismissOrMaximize, true /* snapped */);
} else {
applyDismissSplit(tiles, layout, dismissOrMaximize);
}
}
public void setResizing(final boolean resizing) {
try {
ActivityTaskManager.getService().setSplitScreenResizing(resizing);
} catch (RemoteException e) {
Log.w(TAG, "Error calling setDockedStackResizing: " + e);
}
}
/** Sets a touch region */
public void setTouchRegion(Rect region) {
try {
synchronized (mDockedRect) {
mTouchableRegion.set(region);
}
WindowManagerGlobal.getWindowManagerService().setDockedTaskDividerTouchRegion(
mTouchableRegion);
} catch (RemoteException e) {
Log.w(TAG, "Failed to set touchable region: " + e);
}
}
void applyResizeSplits(int position, LegacySplitDisplayLayout splitLayout) {
WindowContainerTransaction t = new WindowContainerTransaction();
splitLayout.resizeSplits(position, t);
applySyncTransaction(t);
}
boolean getHomeAndRecentsTasks(List<ActivityManager.RunningTaskInfo> out,
WindowContainerToken parent) {
boolean resizable = false;
List<ActivityManager.RunningTaskInfo> rootTasks = parent == null
? mTaskOrganizer.getRootTasks(Display.DEFAULT_DISPLAY, HOME_AND_RECENTS)
: mTaskOrganizer.getChildTasks(parent, HOME_AND_RECENTS);
for (int i = 0, n = rootTasks.size(); i < n; ++i) {
final ActivityManager.RunningTaskInfo ti = rootTasks.get(i);
out.add(ti);
if (ti.topActivityType == ACTIVITY_TYPE_HOME) {
resizable = ti.isResizeable;
}
}
return resizable;
}
/**
* Assign a fixed override-bounds to home tasks that reflect their geometry while the primary
* split is minimized. This actually "sticks out" of the secondary split area, but when in
* minimized mode, the secondary split gets a 'negative' crop to expose it.
*/
boolean applyHomeTasksMinimized(LegacySplitDisplayLayout layout, WindowContainerToken parent,
@NonNull WindowContainerTransaction wct) {
// Resize the home/recents stacks to the larger minimized-state size
final Rect homeBounds;
final ArrayList<ActivityManager.RunningTaskInfo> homeStacks = new ArrayList<>();
boolean isHomeResizable = getHomeAndRecentsTasks(homeStacks, parent);
if (isHomeResizable) {
homeBounds = layout.calcResizableMinimizedHomeStackBounds();
} else {
// home is not resizable, so lock it to its inherent orientation size.
homeBounds = new Rect(0, 0, 0, 0);
for (int i = homeStacks.size() - 1; i >= 0; --i) {
if (homeStacks.get(i).topActivityType == ACTIVITY_TYPE_HOME) {
final int orient = homeStacks.get(i).configuration.orientation;
final boolean displayLandscape = layout.mDisplayLayout.isLandscape();
final boolean isLandscape = orient == ORIENTATION_LANDSCAPE
|| (orient == ORIENTATION_UNDEFINED && displayLandscape);
homeBounds.right = isLandscape == displayLandscape
? layout.mDisplayLayout.width() : layout.mDisplayLayout.height();
homeBounds.bottom = isLandscape == displayLandscape
? layout.mDisplayLayout.height() : layout.mDisplayLayout.width();
break;
}
}
}
for (int i = homeStacks.size() - 1; i >= 0; --i) {
// For non-resizable homes, the minimized size is actually the fullscreen-size. As a
// result, we don't minimize for recents since it only shows half-size screenshots.
if (!isHomeResizable) {
if (homeStacks.get(i).topActivityType == ACTIVITY_TYPE_RECENTS) {
continue;
}
wct.setWindowingMode(homeStacks.get(i).token, WINDOWING_MODE_FULLSCREEN);
}
wct.setBounds(homeStacks.get(i).token, homeBounds);
}
layout.mTiles.mHomeBounds.set(homeBounds);
return isHomeResizable;
}
/** @see #buildEnterSplit */
boolean applyEnterSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout) {
// Set launchtile first so that any stack created after
// getAllRootTaskInfos and before reparent (even if unlikely) are placed
// correctly.
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setLaunchRoot(tiles.mSecondary.token, CONTROLLED_WINDOWING_MODES,
CONTROLLED_ACTIVITY_TYPES);
final boolean isHomeResizable = buildEnterSplit(wct, tiles, layout);
applySyncTransaction(wct);
return isHomeResizable;
}
/**
* Finishes entering split-screen by reparenting all FULLSCREEN tasks into the secondary split.
* This assumes there is already something in the primary split since that is usually what
* triggers a call to this. In the same transaction, this overrides the home task bounds via
* {@link #applyHomeTasksMinimized}.
*
* @return whether the home stack is resizable
*/
boolean buildEnterSplit(WindowContainerTransaction outWct, LegacySplitScreenTaskListener tiles,
LegacySplitDisplayLayout layout) {
List<ActivityManager.RunningTaskInfo> rootTasks =
mTaskOrganizer.getRootTasks(DEFAULT_DISPLAY, null /* activityTypes */);
if (rootTasks.isEmpty()) {
return false;
}
ActivityManager.RunningTaskInfo topHomeTask = null;
for (int i = rootTasks.size() - 1; i >= 0; --i) {
final ActivityManager.RunningTaskInfo rootTask = rootTasks.get(i);
// Check whether the task can be moved to split secondary.
if (!rootTask.supportsMultiWindow && rootTask.topActivityType != ACTIVITY_TYPE_HOME) {
continue;
}
// Only move split controlling tasks to split secondary.
final int windowingMode = rootTask.getWindowingMode();
if (!ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, windowingMode)
|| !ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, rootTask.getActivityType())
// Excludes split screen secondary due to it's the root we're reparenting to.
|| windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
continue;
}
// Since this iterates from bottom to top, update topHomeTask for every fullscreen task
// so it will be left with the status of the top one.
topHomeTask = isHomeOrRecentTask(rootTask) ? rootTask : null;
outWct.reparent(rootTask.token, tiles.mSecondary.token, true /* onTop */);
}
// Move the secondary split-forward.
outWct.reorder(tiles.mSecondary.token, true /* onTop */);
boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */,
outWct);
if (topHomeTask != null && !Transitions.ENABLE_SHELL_TRANSITIONS) {
// Translate/update-crop of secondary out-of-band with sync transaction -- Until BALST
// is enabled, this temporarily syncs the home surface position with offset until
// sync transaction finishes.
outWct.setBoundsChangeTransaction(topHomeTask.token, tiles.mHomeBounds);
}
return isHomeResizable;
}
static boolean isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti) {
final int atype = ti.getActivityType();
return atype == ACTIVITY_TYPE_HOME || atype == ACTIVITY_TYPE_RECENTS;
}
/** @see #buildDismissSplit */
void applyDismissSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout,
boolean dismissOrMaximize) {
// TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished
// plus specific APIs to clean this up.
final WindowContainerTransaction wct = new WindowContainerTransaction();
// Set launch root first so that any task created after getChildContainers and
// before reparent (pretty unlikely) are put into fullscreen.
wct.setLaunchRoot(tiles.mSecondary.token, null, null);
buildDismissSplit(wct, tiles, layout, dismissOrMaximize);
applySyncTransaction(wct);
}
/**
* Reparents all tile members back to their display and resets home task override bounds.
* @param dismissOrMaximize When {@code true} this resolves the split by closing the primary
* split (thus resulting in the top of the secondary split becoming
* fullscreen. {@code false} resolves the other way.
*/
static void buildDismissSplit(WindowContainerTransaction outWct,
LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout,
boolean dismissOrMaximize) {
// TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished
// plus specific APIs to clean this up.
final TaskOrganizer taskOrg = tiles.getTaskOrganizer();
List<ActivityManager.RunningTaskInfo> primaryChildren =
taskOrg.getChildTasks(tiles.mPrimary.token, null /* activityTypes */);
List<ActivityManager.RunningTaskInfo> secondaryChildren =
taskOrg.getChildTasks(tiles.mSecondary.token, null /* activityTypes */);
// In some cases (eg. non-resizable is launched), system-server will leave split-screen.
// as a result, the above will not capture any tasks; yet, we need to clean-up the
// home task bounds.
List<ActivityManager.RunningTaskInfo> freeHomeAndRecents =
taskOrg.getRootTasks(DEFAULT_DISPLAY, HOME_AND_RECENTS);
// Filter out the root split tasks
freeHomeAndRecents.removeIf(p -> p.token.equals(tiles.mSecondary.token)
|| p.token.equals(tiles.mPrimary.token));
if (primaryChildren.isEmpty() && secondaryChildren.isEmpty()
&& freeHomeAndRecents.isEmpty()) {
return;
}
if (dismissOrMaximize) {
// Dismissing, so move all primary split tasks first
for (int i = primaryChildren.size() - 1; i >= 0; --i) {
outWct.reparent(primaryChildren.get(i).token, null /* parent */,
true /* onTop */);
}
boolean homeOnTop = false;
// Don't need to worry about home tasks because they are already in the "proper"
// order within the secondary split.
for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i);
outWct.reparent(ti.token, null /* parent */, true /* onTop */);
if (isHomeOrRecentTask(ti)) {
outWct.setBounds(ti.token, null);
outWct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED);
if (i == 0) {
homeOnTop = true;
}
}
}
if (homeOnTop && !Transitions.ENABLE_SHELL_TRANSITIONS) {
// Translate/update-crop of secondary out-of-band with sync transaction -- instead
// play this in sync with new home-app frame because until BALST is enabled this
// shows up on screen before the syncTransaction returns.
// We only have access to the secondary root surface, though, so in order to
// position things properly, we have to take into account the existing negative
// offset/crop of the minimized-home task.
final boolean landscape = layout.mDisplayLayout.isLandscape();
final int posX = landscape ? layout.mSecondary.left - tiles.mHomeBounds.left
: layout.mSecondary.left;
final int posY = landscape ? layout.mSecondary.top
: layout.mSecondary.top - tiles.mHomeBounds.top;
final SurfaceControl.Transaction sft = new SurfaceControl.Transaction();
sft.setPosition(tiles.mSecondarySurface, posX, posY);
final Rect crop = new Rect(0, 0, layout.mDisplayLayout.width(),
layout.mDisplayLayout.height());
crop.offset(-posX, -posY);
sft.setWindowCrop(tiles.mSecondarySurface, crop);
outWct.setBoundsChangeTransaction(tiles.mSecondary.token, sft);
}
} else {
// Maximize, so move non-home secondary split first
for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
if (isHomeOrRecentTask(secondaryChildren.get(i))) {
continue;
}
outWct.reparent(secondaryChildren.get(i).token, null /* parent */,
true /* onTop */);
}
// Find and place home tasks in-between. This simulates the fact that there was
// nothing behind the primary split's tasks.
for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i);
if (isHomeOrRecentTask(ti)) {
outWct.reparent(ti.token, null /* parent */, true /* onTop */);
// reset bounds and mode too
outWct.setBounds(ti.token, null);
outWct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED);
}
}
for (int i = primaryChildren.size() - 1; i >= 0; --i) {
outWct.reparent(primaryChildren.get(i).token, null /* parent */,
true /* onTop */);
}
}
for (int i = freeHomeAndRecents.size() - 1; i >= 0; --i) {
outWct.setBounds(freeHomeAndRecents.get(i).token, null);
outWct.setWindowingMode(freeHomeAndRecents.get(i).token, WINDOWING_MODE_UNDEFINED);
}
// Reset focusable to true
outWct.setFocusable(tiles.mPrimary.token, true /* focusable */);
}
/**
* Utility to apply a sync transaction serially with other sync transactions.
*
* @see SyncTransactionQueue#queue
*/
void applySyncTransaction(WindowContainerTransaction wct) {
mSyncTransactionQueue.queue(wct);
}
/**
* @see SyncTransactionQueue#queueIfWaiting
*/
boolean queueSyncTransactionIfWaiting(WindowContainerTransaction wct) {
return mSyncTransactionQueue.queueIfWaiting(wct);
}
/**
* @see SyncTransactionQueue#runInSync
*/
void runInSync(SyncTransactionQueue.TransactionRunnable runnable) {
mSyncTransactionQueue.runInSync(runnable);
}
}

View File

@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
import android.content.res.Resources
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.wm.shell.flicker.testapp.Components
@@ -32,11 +31,6 @@ class SplitScreenHelper(
const val TEST_REPETITIONS = 1
const val TIMEOUT_MS = 3_000L
// TODO: remove all legacy split screen flicker tests when legacy split screen is fully
// deprecated.
fun isUsingLegacySplit(): Boolean =
Resources.getSystem().getBoolean(com.android.internal.R.bool.config_useLegacySplit)
fun getPrimary(instrumentation: Instrumentation): SplitScreenHelper =
SplitScreenHelper(instrumentation,
Components.SplitScreenActivity.LABEL,

View File

@@ -1,116 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.flicker.legacysplitscreen
import android.platform.test.annotations.Presubmit
import android.view.Surface
import android.view.WindowManagerPolicyConstants
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test open activity and dock to primary split screen
* To run this test: `atest WMShellFlickerTests:EnterSplitScreenDockActivity`
*/
@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group4
class EnterSplitScreenDockActivity(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
transitions {
device.launchSplitScreen(wmHelper)
}
}
override val ignoredWindows: List<FlickerComponentName>
get() = listOf(LAUNCHER_COMPONENT, LIVE_WALLPAPER_COMPONENT,
splitScreenApp.component, FlickerComponentName.SPLASH_SCREEN,
FlickerComponentName.SNAPSHOT, LAUNCHER_COMPONENT)
@Presubmit
@Test
fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
splitScreenApp.component)
@Presubmit
@Test
fun dockedStackDividerBecomesVisible() = testSpec.dockedStackDividerBecomesVisible()
@Presubmit
@Test
fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Presubmit
@Test
fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@Presubmit
@Test
fun appWindowIsVisible() {
testSpec.assertWmEnd {
isAppWindowVisible(splitScreenApp.component)
}
}
@FlakyTest
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
repetitions = SplitScreenHelper.TEST_REPETITIONS,
supportedRotations = listOf(Surface.ROTATION_0), // bugId = 179116910
supportedNavigationModes = listOf(
WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
)
}
}
}

View File

@@ -1,104 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.flicker.legacysplitscreen
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test enter split screen from a detached recent task
*
* To run this test: `atest WMShellFlickerTests:EnterSplitScreenFromDetachedRecentTask`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@Group4
class EnterSplitScreenFromDetachedRecentTask(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
override val transition: FlickerBuilder.() -> Unit
get() = {
cleanSetup(this)
setup {
eachRun {
splitScreenApp.launchViaIntent(wmHelper)
// Press back to remove the task, but it should still be shown in recent.
device.pressBack()
}
}
transitions {
device.launchSplitScreen(wmHelper)
}
}
override val ignoredWindows: List<FlickerComponentName>
get() = listOf(LAUNCHER_COMPONENT,
FlickerComponentName.SPLASH_SCREEN,
FlickerComponentName.SNAPSHOT,
splitScreenApp.component)
@Presubmit
@Test
fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
@Presubmit
@Test
fun appWindowIsVisible() {
testSpec.assertWmEnd {
isAppWindowVisible(splitScreenApp.component)
}
}
@Presubmit
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
repetitions = SplitScreenHelper.TEST_REPETITIONS,
supportedRotations = listOf(Surface.ROTATION_0) // bugId = 179116910
)
}
}
}

View File

@@ -1,126 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.flicker.legacysplitscreen
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test open activity to primary split screen and dock secondary activity to side
* To run this test: `atest WMShellFlickerTests:EnterSplitScreenLaunchToSide`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group4
class EnterSplitScreenLaunchToSide(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
transitions {
device.launchSplitScreen(wmHelper)
device.reopenAppFromOverview(wmHelper)
}
}
override val ignoredWindows: List<FlickerComponentName>
get() = listOf(LAUNCHER_COMPONENT, splitScreenApp.component,
secondaryApp.component, FlickerComponentName.SPLASH_SCREEN,
FlickerComponentName.SNAPSHOT)
@Presubmit
@Test
fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
splitScreenApp.component)
@Presubmit
@Test
fun dockedStackSecondaryBoundsIsVisibleAtEnd() =
testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.startRotation,
secondaryApp.component)
@Presubmit
@Test
fun dockedStackDividerBecomesVisible() = testSpec.dockedStackDividerBecomesVisible()
@Presubmit
@Test
fun appWindowBecomesVisible() {
testSpec.assertWm {
// when the app is launched, first the activity becomes visible, then the
// SnapshotStartingWindow appears and then the app window becomes visible.
// Because we log WM once per frame, sometimes the activity and the window
// become visible in the same entry, sometimes not, thus it is not possible to
// assert the visibility of the activity here
this.isAppWindowInvisible(secondaryApp.component)
.then()
// during re-parenting, the window may disappear and reappear from the
// trace, this occurs because we log only 1x per frame
.notContains(secondaryApp.component, isOptional = true)
.then()
.isAppWindowVisible(secondaryApp.component)
}
}
@Presubmit
@Test
fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Presubmit
@Test
fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@Presubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
repetitions = SplitScreenHelper.TEST_REPETITIONS,
supportedRotations = listOf(Surface.ROTATION_0) // bugId = 175687842
)
}
}
}

View File

@@ -1,110 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.flicker.legacysplitscreen
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.canSplitScreen
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.After
import org.junit.Assert
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
/**
* Test enter split screen from non-resizable activity. When the device doesn't support
* non-resizable in multi window, there should be no button to enter split screen for non-resizable
* activity.
*
* To run this test: `atest WMShellFlickerTests:EnterSplitScreenNotSupportNonResizable`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@Group4
class EnterSplitScreenNotSupportNonResizable(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
override val transition: FlickerBuilder.() -> Unit
get() = {
cleanSetup(this)
setup {
eachRun {
nonResizeableApp.launchViaIntent(wmHelper)
}
}
transitions {
if (device.canSplitScreen(wmHelper)) {
Assert.fail("Non-resizeable app should not enter split screen")
}
}
}
override val ignoredWindows: List<FlickerComponentName>
get() = listOf(LAUNCHER_COMPONENT,
FlickerComponentName.SPLASH_SCREEN,
FlickerComponentName.SNAPSHOT,
nonResizeableApp.component,
splitScreenApp.component)
@Before
override fun setup() {
super.setup()
setSupportsNonResizableMultiWindow(instrumentation, -1)
}
@After
override fun teardown() {
super.teardown()
resetMultiWindowConfig(instrumentation)
}
@Presubmit
@Test
fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd()
@Presubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
repetitions = SplitScreenHelper.TEST_REPETITIONS,
supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
}
}
}

View File

@@ -1,115 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.flicker.legacysplitscreen
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.After
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
/**
* Test enter split screen from non-resizable activity. When the device supports
* non-resizable in multi window, there should be a button to enter split screen for non-resizable
* activity.
*
* To run this test: `atest WMShellFlickerTests:EnterSplitScreenSupportNonResizable`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@Group2
class EnterSplitScreenSupportNonResizable(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
override val transition: FlickerBuilder.() -> Unit
get() = {
cleanSetup(this)
setup {
eachRun {
nonResizeableApp.launchViaIntent(wmHelper)
}
}
transitions {
device.launchSplitScreen(wmHelper)
}
}
override val ignoredWindows: List<FlickerComponentName>
get() = listOf(LAUNCHER_COMPONENT,
FlickerComponentName.SPLASH_SCREEN,
FlickerComponentName.SNAPSHOT,
nonResizeableApp.component,
splitScreenApp.component)
@Before
override fun setup() {
super.setup()
setSupportsNonResizableMultiWindow(instrumentation, 1)
}
@After
override fun teardown() {
super.teardown()
resetMultiWindowConfig(instrumentation)
}
@Presubmit
@Test
fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
@Presubmit
@Test
fun appWindowIsVisible() {
testSpec.assertWmEnd {
isAppWindowVisible(nonResizeableApp.component)
}
}
@Presubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
repetitions = SplitScreenHelper.TEST_REPETITIONS,
supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
}
}
}

View File

@@ -1,124 +0,0 @@
/*
* 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.wm.shell.flicker.legacysplitscreen
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.exitSplitScreenFromBottom
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test open resizeable activity split in primary, and drag divider to bottom exit split screen
* To run this test: `atest WMShellFlickerTests:ExitLegacySplitScreenFromBottom`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group2
class ExitLegacySplitScreenFromBottom(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
setup {
eachRun {
splitScreenApp.launchViaIntent(wmHelper)
device.launchSplitScreen(wmHelper)
}
}
teardown {
eachRun {
splitScreenApp.exit(wmHelper)
}
}
transitions {
device.exitSplitScreenFromBottom(wmHelper)
}
}
override val ignoredWindows: List<FlickerComponentName>
get() = listOf(LAUNCHER_COMPONENT, FlickerComponentName.SPLASH_SCREEN,
splitScreenApp.component, secondaryApp.component,
FlickerComponentName.SNAPSHOT)
@FlakyTest
@Test
fun layerBecomesInvisible() {
testSpec.assertLayers {
this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
.then()
.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
}
}
@FlakyTest
@Test
fun appWindowBecomesInVisible() {
testSpec.assertWm {
this.isAppWindowVisible(secondaryApp.component)
.then()
.isAppWindowInvisible(secondaryApp.component)
}
}
@FlakyTest
@Test
fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@FlakyTest
@Test
fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@FlakyTest
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
super.visibleLayersShownMoreThanOneConsecutiveEntry()
@FlakyTest
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
repetitions = SplitScreenHelper.TEST_REPETITIONS,
supportedRotations = listOf(Surface.ROTATION_0) // b/175687842
)
}
}
}

View File

@@ -1,129 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.flicker.legacysplitscreen
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test dock activity to primary split screen, and open secondary to side, exit primary split
* and test secondary activity become full screen.
* To run this test: `atest WMShellFlickerTests:ExitPrimarySplitScreenShowSecondaryFullscreen`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group2
class ExitPrimarySplitScreenShowSecondaryFullscreen(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
teardown {
eachRun {
secondaryApp.exit(wmHelper)
}
}
transitions {
splitScreenApp.launchViaIntent(wmHelper)
secondaryApp.launchViaIntent(wmHelper)
device.launchSplitScreen(wmHelper)
device.reopenAppFromOverview(wmHelper)
// TODO(b/175687842) Can not find Split screen divider, use exit() instead
splitScreenApp.exit(wmHelper)
}
}
override val ignoredWindows: List<FlickerComponentName>
get() = listOf(LAUNCHER_COMPONENT, FlickerComponentName.SPLASH_SCREEN,
splitScreenApp.component, secondaryApp.component,
FlickerComponentName.SNAPSHOT)
@Presubmit
@Test
fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd()
@FlakyTest
@Test
fun layerBecomesInvisible() {
testSpec.assertLayers {
this.isVisible(splitScreenApp.component)
.then()
.isInvisible(splitScreenApp.component)
}
}
@FlakyTest
@Test
fun appWindowBecomesInVisible() {
testSpec.assertWm {
this.isAppWindowVisible(splitScreenApp.component)
.then()
.isAppWindowInvisible(splitScreenApp.component)
}
}
@Presubmit
@Test
fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Presubmit
@Test
fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@Presubmit
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
repetitions = SplitScreenHelper.TEST_REPETITIONS,
supportedRotations = listOf(Surface.ROTATION_0) // bugId = 179116910
)
}
}
}

View File

@@ -1,196 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.flicker.legacysplitscreen
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.After
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
/**
* Test launch non-resizable activity via intent in split screen mode. When the device does not
* support non-resizable in multi window, it should trigger exit split screen.
* To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromIntentNotSupportNonResizable`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group2
class LegacySplitScreenFromIntentNotSupportNonResizable(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
override val transition: FlickerBuilder.() -> Unit
get() = {
cleanSetup(this)
setup {
eachRun {
splitScreenApp.launchViaIntent(wmHelper)
device.launchSplitScreen(wmHelper)
}
}
transitions {
nonResizeableApp.launchViaIntent(wmHelper)
wmHelper.waitForAppTransitionIdle()
}
}
override val ignoredWindows: List<FlickerComponentName>
get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT,
nonResizeableApp.component, splitScreenApp.component,
FlickerComponentName.SPLASH_SCREEN,
FlickerComponentName.SNAPSHOT)
@Before
override fun setup() {
super.setup()
setSupportsNonResizableMultiWindow(instrumentation, -1)
}
@After
override fun teardown() {
super.teardown()
resetMultiWindowConfig(instrumentation)
}
@Presubmit
@Test
fun resizableAppLayerBecomesInvisible() {
testSpec.assertLayers {
this.isVisible(splitScreenApp.component)
.then()
.isInvisible(splitScreenApp.component)
}
}
@Presubmit
@Test
fun nonResizableAppLayerBecomesVisible() {
testSpec.assertLayers {
this.notContains(nonResizeableApp.component)
.then()
.isInvisible(nonResizeableApp.component)
.then()
.isVisible(nonResizeableApp.component)
}
}
/**
* Assets that [splitScreenApp] exists at the start of the trace and, once it becomes
* invisible, it remains invisible until the end of the trace.
*/
@Presubmit
@Test
fun resizableAppWindowBecomesInvisible() {
testSpec.assertWm {
// when the activity gets PAUSED the window may still be marked as visible
// it will be updated in the next log entry. This occurs because we record 1x
// per frame, thus ignore activity check here
this.isAppWindowVisible(splitScreenApp.component)
.then()
// immediately after the window (after onResume and before perform relayout)
// the activity is invisible. This may or not be logged, since we record 1x
// per frame, thus ignore activity check here
.isAppWindowInvisible(splitScreenApp.component)
}
}
/**
* Assets that [nonResizeableApp] doesn't exist at the start of the trace, then
* [nonResizeableApp] is created (visible or not) and, once [nonResizeableApp] becomes
* visible, it remains visible until the end of the trace.
*/
@Presubmit
@Test
fun nonResizableAppWindowBecomesVisible() {
testSpec.assertWm {
this.notContains(nonResizeableApp.component)
.then()
// we log once per frame, upon logging, window may be visible or not depending
// on what was processed until that moment. Both behaviors are correct
.isAppWindowInvisible(nonResizeableApp.component, isOptional = true)
.then()
// immediately after the window (after onResume and before perform relayout)
// the activity is invisible. This may or not be logged, since we record 1x
// per frame, thus ignore activity check here
.isAppWindowVisible(nonResizeableApp.component)
}
}
/**
* Asserts that both the app window and the activity are visible at the end of the trace
*/
@Presubmit
@Test
fun nonResizableAppWindowBecomesVisibleAtEnd() {
testSpec.assertWmEnd {
isAppWindowVisible(nonResizeableApp.component)
}
}
@Presubmit
@Test
fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd()
@Presubmit
@Test
fun onlyNonResizableAppWindowIsVisibleAtEnd() {
testSpec.assertWmEnd {
isAppWindowInvisible(splitScreenApp.component)
isAppWindowVisible(nonResizeableApp.component)
}
}
@Presubmit
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
repetitions = SplitScreenHelper.TEST_REPETITIONS,
supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
}
}
}

View File

@@ -1,153 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.flicker.legacysplitscreen
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.After
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
/**
* Test launch non-resizable activity via intent in split screen mode. When the device supports
* non-resizable in multi window, it should show the non-resizable app in split screen.
* To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromIntentSupportNonResizable`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group2
class LegacySplitScreenFromIntentSupportNonResizable(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
override val transition: FlickerBuilder.() -> Unit
get() = {
cleanSetup(this)
setup {
eachRun {
splitScreenApp.launchViaIntent(wmHelper)
device.launchSplitScreen(wmHelper)
}
}
transitions {
nonResizeableApp.launchViaIntent(wmHelper)
wmHelper.waitForAppTransitionIdle()
}
}
override val ignoredWindows: List<FlickerComponentName>
get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT,
nonResizeableApp.component, splitScreenApp.component,
FlickerComponentName.SPLASH_SCREEN,
FlickerComponentName.SNAPSHOT)
@Before
override fun setup() {
super.setup()
setSupportsNonResizableMultiWindow(instrumentation, 1)
}
@After
override fun teardown() {
super.teardown()
resetMultiWindowConfig(instrumentation)
}
@Presubmit
@Test
fun nonResizableAppLayerBecomesVisible() {
testSpec.assertLayers {
this.isInvisible(nonResizeableApp.component)
.then()
.isVisible(nonResizeableApp.component)
}
}
/**
* Assets that [nonResizeableApp] doesn't exist at the start of the trace, then
* [nonResizeableApp] is created (visible or not) and, once [nonResizeableApp] becomes
* visible, it remains visible until the end of the trace.
*/
@Presubmit
@Test
fun nonResizableAppWindowBecomesVisible() {
testSpec.assertWm {
this.notContains(nonResizeableApp.component)
.then()
// we log once per frame, upon logging, window may be visible or not depending
// on what was processed until that moment. Both behaviors are correct
.isAppWindowInvisible(nonResizeableApp.component, isOptional = true)
.then()
// immediately after the window (after onResume and before perform relayout)
// the activity is invisible. This may or not be logged, since we record 1x
// per frame, thus ignore activity check here
.isAppWindowVisible(nonResizeableApp.component)
}
}
@Presubmit
@Test
fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
@Presubmit
@Test
fun bothAppsWindowsAreVisibleAtEnd() {
testSpec.assertWmEnd {
isAppWindowVisible(splitScreenApp.component)
isAppWindowVisible(nonResizeableApp.component)
}
}
@Presubmit
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
repetitions = SplitScreenHelper.TEST_REPETITIONS,
supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
}
}
}

View File

@@ -1,169 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.flicker.legacysplitscreen
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.After
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
/**
* Test launch non-resizable activity via recent overview in split screen mode. When the device does
* not support non-resizable in multi window, it should trigger exit split screen.
* To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromRecentNotSupportNonResizable`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group2
class LegacySplitScreenFromRecentNotSupportNonResizable(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
override val transition: FlickerBuilder.() -> Unit
get() = {
cleanSetup(this)
setup {
eachRun {
nonResizeableApp.launchViaIntent(wmHelper)
splitScreenApp.launchViaIntent(wmHelper)
device.launchSplitScreen(wmHelper)
}
}
transitions {
device.reopenAppFromOverview(wmHelper)
}
}
override val ignoredWindows: List<FlickerComponentName>
get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT,
TOAST_COMPONENT, splitScreenApp.component, nonResizeableApp.component,
FlickerComponentName.SPLASH_SCREEN,
FlickerComponentName.SNAPSHOT)
@Before
override fun setup() {
super.setup()
setSupportsNonResizableMultiWindow(instrumentation, -1)
}
@After
override fun teardown() {
super.teardown()
resetMultiWindowConfig(instrumentation)
}
@Presubmit
@Test
fun resizableAppLayerBecomesInvisible() {
testSpec.assertLayers {
this.isVisible(splitScreenApp.component)
.then()
.isInvisible(splitScreenApp.component)
}
}
@Presubmit
@Test
fun nonResizableAppLayerBecomesVisible() {
testSpec.assertLayers {
this.isInvisible(nonResizeableApp.component)
.then()
.isVisible(nonResizeableApp.component)
}
}
@Presubmit
@Test
fun resizableAppWindowBecomesInvisible() {
testSpec.assertWm {
// when the activity gets PAUSED the window may still be marked as visible
// it will be updated in the next log entry. This occurs because we record 1x
// per frame, thus ignore activity check here
this.isAppWindowVisible(splitScreenApp.component)
.then()
// immediately after the window (after onResume and before perform relayout)
// the activity is invisible. This may or not be logged, since we record 1x
// per frame, thus ignore activity check here
.isAppWindowInvisible(splitScreenApp.component)
}
}
@FlakyTest
@Test
fun nonResizableAppWindowBecomesVisible() {
testSpec.assertWm {
this.isAppWindowInvisible(nonResizeableApp.component)
.then()
.isAppWindowVisible(nonResizeableApp.component)
}
}
@Presubmit
@Test
fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd()
@Presubmit
@Test
fun onlyNonResizableAppWindowIsVisibleAtEnd() {
testSpec.assertWmEnd {
isAppWindowInvisible(splitScreenApp.component)
isAppWindowVisible(nonResizeableApp.component)
}
}
@Presubmit
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
repetitions = SplitScreenHelper.TEST_REPETITIONS,
supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
}
}
}

View File

@@ -1,155 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.flicker.legacysplitscreen
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.After
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
/**
* Test launch non-resizable activity via recent overview in split screen mode. When the device
* supports non-resizable in multi window, it should show the non-resizable app in split screen.
* To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromRecentSupportNonResizable`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group2
class LegacySplitScreenFromRecentSupportNonResizable(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
override val transition: FlickerBuilder.() -> Unit
get() = {
cleanSetup(this)
setup {
eachRun {
nonResizeableApp.launchViaIntent(wmHelper)
splitScreenApp.launchViaIntent(wmHelper)
device.launchSplitScreen(wmHelper)
}
}
transitions {
device.reopenAppFromOverview(wmHelper)
}
}
override val ignoredWindows: List<FlickerComponentName>
get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT,
TOAST_COMPONENT, splitScreenApp.component, nonResizeableApp.component,
FlickerComponentName.SPLASH_SCREEN,
FlickerComponentName.SNAPSHOT)
@Before
override fun setup() {
super.setup()
setSupportsNonResizableMultiWindow(instrumentation, 1)
}
@After
override fun teardown() {
super.teardown()
resetMultiWindowConfig(instrumentation)
}
@Presubmit
@Test
fun nonResizableAppLayerBecomesVisible() {
testSpec.assertLayers {
this.isInvisible(nonResizeableApp.component)
.then()
.isVisible(nonResizeableApp.component)
}
}
@Presubmit
@Test
fun nonResizableAppWindowBecomesVisible() {
testSpec.assertWm {
// when the app is launched, first the activity becomes visible, then the
// SnapshotStartingWindow appears and then the app window becomes visible.
// Because we log WM once per frame, sometimes the activity and the window
// become visible in the same entry, sometimes not, thus it is not possible to
// assert the visibility of the activity here
this.isAppWindowInvisible(nonResizeableApp.component)
.then()
// during re-parenting, the window may disappear and reappear from the
// trace, this occurs because we log only 1x per frame
.notContains(nonResizeableApp.component, isOptional = true)
.then()
// if the window reappears after re-parenting it will most likely not
// be visible in the first log entry (because we log only 1x per frame)
.isAppWindowInvisible(nonResizeableApp.component, isOptional = true)
.then()
.isAppWindowVisible(nonResizeableApp.component)
}
}
@Presubmit
@Test
fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
@Presubmit
@Test
fun bothAppsWindowsAreVisibleAtEnd() {
testSpec.assertWmEnd {
isAppWindowVisible(splitScreenApp.component)
isAppWindowVisible(nonResizeableApp.component)
}
}
@Presubmit
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
repetitions = SplitScreenHelper.TEST_REPETITIONS,
supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
}
}
}

View File

@@ -1,47 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.flicker.legacysplitscreen
import android.view.Surface
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
abstract class LegacySplitScreenRotateTransition(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
override val transition: FlickerBuilder.() -> Unit
get() = {
setup {
eachRun {
device.wakeUpAndGoToHomeScreen()
device.openQuickStepAndClearRecentAppsFromOverview(wmHelper)
secondaryApp.launchViaIntent(wmHelper)
splitScreenApp.launchViaIntent(wmHelper)
}
}
teardown {
eachRun {
splitScreenApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
this.setRotation(Surface.ROTATION_0)
}
}
}
}

View File

@@ -1,160 +0,0 @@
/*
* 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.wm.shell.flicker.legacysplitscreen
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.exitSplitScreen
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.dockedStackDividerBecomesInvisible
import com.android.wm.shell.flicker.helpers.SimpleAppHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test open app to split screen.
* To run this test: `atest WMShellFlickerTests:LegacySplitScreenToLauncher`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group2
class LegacySplitScreenToLauncher(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
private val testApp = SimpleAppHelper(instrumentation)
override val transition: FlickerBuilder.() -> Unit
get() = {
setup {
test {
device.wakeUpAndGoToHomeScreen()
device.openQuickStepAndClearRecentAppsFromOverview(wmHelper)
}
eachRun {
testApp.launchViaIntent(wmHelper)
this.setRotation(testSpec.endRotation)
device.launchSplitScreen(wmHelper)
device.waitForIdle()
}
}
teardown {
eachRun {
testApp.exit(wmHelper)
}
}
transitions {
device.exitSplitScreen()
}
}
override val ignoredWindows: List<FlickerComponentName>
get() = listOf(LAUNCHER_COMPONENT, FlickerComponentName.SPLASH_SCREEN,
FlickerComponentName.SNAPSHOT)
@Presubmit
@Test
fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Presubmit
@Test
fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@Presubmit
@Test
fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible()
@Presubmit
@Test
fun entireScreenCovered() = testSpec.entireScreenCovered()
@Presubmit
@Test
fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@FlakyTest(bugId = 206753786)
@Test
fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
@FlakyTest
@Test
fun dockedStackDividerBecomesInvisible() = testSpec.dockedStackDividerBecomesInvisible()
@FlakyTest
@Test
fun layerBecomesInvisible() {
testSpec.assertLayers {
this.isVisible(testApp.component)
.then()
.isInvisible(testApp.component)
}
}
@FlakyTest
@Test
fun focusDoesNotChange() {
testSpec.assertEventLog {
this.focusDoesNotChange()
}
}
@Presubmit
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
// b/161435597 causes the test not to work on 90 degrees
return FlickerTestParameterFactory.getInstance()
.getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
}
}
}

View File

@@ -1,149 +0,0 @@
/*
* 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/LICENSE2.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.legacysplitscreen
import android.app.Instrumentation
import android.content.Context
import android.support.test.launcherhelper.LauncherStrategyFactory
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.getDevEnableNonResizableMultiWindow
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setDevEnableNonResizableMultiWindow
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.After
import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Test
abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestParameter) {
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
protected val context: Context = instrumentation.context
protected val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation)
protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
protected val nonResizeableApp = SplitScreenHelper.getNonResizeable(instrumentation)
protected val LAUNCHER_COMPONENT = FlickerComponentName("",
LauncherStrategyFactory.getInstance(instrumentation)
.launcherStrategy.supportedLauncherPackage)
private var prevDevEnableNonResizableMultiWindow = 0
@Before
open fun setup() {
// Only run legacy split tests when the system is using legacy split screen.
assumeTrue(SplitScreenHelper.isUsingLegacySplit())
// Legacy split is having some issue with Shell transition, and will be deprecated soon.
assumeFalse(isShellTransitionsEnabled())
prevDevEnableNonResizableMultiWindow = getDevEnableNonResizableMultiWindow(context)
if (prevDevEnableNonResizableMultiWindow != 0) {
// Turn off the development option
setDevEnableNonResizableMultiWindow(context, 0)
}
}
@After
open fun teardown() {
setDevEnableNonResizableMultiWindow(context, prevDevEnableNonResizableMultiWindow)
}
/**
* List of windows that are ignored when verifying that visible elements appear on 2
* consecutive entries in the trace.
*
* b/182720234
*/
open val ignoredWindows: List<FlickerComponentName> = listOf(
FlickerComponentName.SPLASH_SCREEN,
FlickerComponentName.SNAPSHOT)
protected open val transition: FlickerBuilder.() -> Unit
get() = {
setup {
eachRun {
device.wakeUpAndGoToHomeScreen()
device.openQuickStepAndClearRecentAppsFromOverview(wmHelper)
secondaryApp.launchViaIntent(wmHelper)
splitScreenApp.launchViaIntent(wmHelper)
this.setRotation(testSpec.startRotation)
}
}
teardown {
eachRun {
secondaryApp.exit(wmHelper)
splitScreenApp.exit(wmHelper)
this.setRotation(Surface.ROTATION_0)
}
}
}
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
transition(this)
}
}
internal open val cleanSetup: FlickerBuilder.() -> Unit
get() = {
setup {
eachRun {
device.wakeUpAndGoToHomeScreen()
device.openQuickStepAndClearRecentAppsFromOverview(wmHelper)
this.setRotation(testSpec.startRotation)
}
}
teardown {
eachRun {
nonResizeableApp.exit(wmHelper)
splitScreenApp.exit(wmHelper)
device.pressHome()
this.setRotation(Surface.ROTATION_0)
}
}
}
@FlakyTest(bugId = 178447631)
@Test
open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
testSpec.assertWm {
this.visibleWindowsShownMoreThanOneConsecutiveEntry(ignoredWindows)
}
}
@FlakyTest(bugId = 178447631)
@Test
open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
testSpec.assertLayers {
this.visibleLayersShownMoreThanOneConsecutiveEntry(ignoredWindows)
}
}
companion object {
internal val LIVE_WALLPAPER_COMPONENT = FlickerComponentName("",
"com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2")
internal val LETTERBOX_COMPONENT = FlickerComponentName("", "Letterbox")
internal val TOAST_COMPONENT = FlickerComponentName("", "Toast")
}
}

View File

@@ -1,2 +0,0 @@
# window manager > wm shell > Split Screen
# Bug component: 928697

View File

@@ -1,122 +0,0 @@
/*
* 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.wm.shell.flicker.legacysplitscreen
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.appPairsDividerBecomesVisible
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test open app to split screen.
* To run this test: `atest WMShellFlickerTests:OpenAppToLegacySplitScreen`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group2
class OpenAppToLegacySplitScreen(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
transitions {
device.launchSplitScreen(wmHelper)
wmHelper.waitForAppTransitionIdle()
}
}
override val ignoredWindows: List<FlickerComponentName>
get() = listOf(LAUNCHER_COMPONENT, splitScreenApp.component,
FlickerComponentName.SPLASH_SCREEN,
FlickerComponentName.SNAPSHOT)
@FlakyTest
@Test
fun appWindowBecomesVisible() {
testSpec.assertWm {
this.isAppWindowInvisible(splitScreenApp.component)
.then()
.isAppWindowVisible(splitScreenApp.component)
}
}
@Presubmit
@Test
fun entireScreenCovered() = testSpec.entireScreenCovered()
@Presubmit
@Test
fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
@Presubmit
@Test
fun appPairsDividerBecomesVisible() = testSpec.appPairsDividerBecomesVisible()
@FlakyTest
@Test
fun layerBecomesVisible() {
testSpec.assertLayers {
this.isInvisible(splitScreenApp.component)
.then()
.isVisible(splitScreenApp.component)
}
}
@Presubmit
@Test
fun focusChanges() {
testSpec.assertEventLog {
this.focusChanges(splitScreenApp.`package`,
"recents_animation_input_consumer", "NexusLauncherActivity")
}
}
@Presubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
repetitions = SplitScreenHelper.TEST_REPETITIONS,
supportedRotations = listOf(Surface.ROTATION_0) // bugId = 179116910
)
}
}
}

View File

@@ -1,228 +0,0 @@
/*
* 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.wm.shell.flicker.legacysplitscreen
import android.util.Rational
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.resizeSplitScreen
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.region.Region
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.helpers.SimpleAppHelper
import com.android.wm.shell.flicker.testapp.Components
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test split screen resizing window transitions.
* To run this test: `atest WMShellFlickerTests:ResizeLegacySplitScreen`
*
* Currently it runs only in 0 degrees because of b/156100803
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@FlakyTest(bugId = 159096424)
@Group2
class ResizeLegacySplitScreen(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
private val testAppTop = SimpleAppHelper(instrumentation)
private val testAppBottom = ImeAppHelper(instrumentation)
override val transition: FlickerBuilder.() -> Unit
get() = {
setup {
eachRun {
device.wakeUpAndGoToHomeScreen()
this.setRotation(testSpec.startRotation)
this.launcherStrategy.clearRecentAppsFromOverview()
testAppBottom.launchViaIntent(wmHelper)
device.pressHome()
testAppTop.launchViaIntent(wmHelper)
device.waitForIdle()
device.launchSplitScreen(wmHelper)
val snapshot =
device.findObject(By.res(device.launcherPackageName, "snapshot"))
snapshot.click()
testAppBottom.openIME(device)
device.pressBack()
device.resizeSplitScreen(startRatio)
}
}
teardown {
eachRun {
testAppTop.exit(wmHelper)
testAppBottom.exit(wmHelper)
}
}
transitions {
device.resizeSplitScreen(stopRatio)
}
}
@Test
fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Test
fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@FlakyTest(bugId = 156223549)
@Test
fun topAppWindowIsAlwaysVisible() {
testSpec.assertWm {
this.isAppWindowVisible(Components.SimpleActivity.COMPONENT.toFlickerComponent())
}
}
@FlakyTest(bugId = 156223549)
@Test
fun bottomAppWindowIsAlwaysVisible() {
testSpec.assertWm {
this.isAppWindowVisible(Components.ImeActivity.COMPONENT.toFlickerComponent())
}
}
@Test
fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible()
@Test
fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
@Test
fun entireScreenCovered() = testSpec.entireScreenCovered()
@Test
fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@FlakyTest(bugId = 206753786)
@Test
fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Test
fun topAppLayerIsAlwaysVisible() {
testSpec.assertLayers {
this.isVisible(Components.SimpleActivity.COMPONENT.toFlickerComponent())
}
}
@Test
fun bottomAppLayerIsAlwaysVisible() {
testSpec.assertLayers {
this.isVisible(Components.ImeActivity.COMPONENT.toFlickerComponent())
}
}
@Test
fun dividerLayerIsAlwaysVisible() {
testSpec.assertLayers {
this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
}
}
@FlakyTest
@Test
fun appsStartingBounds() {
testSpec.assertLayersStart {
val displayBounds = WindowUtils.displayBounds
val dividerBounds =
layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region.bounds
val topAppBounds = Region.from(0, 0, dividerBounds.right,
dividerBounds.top + WindowUtils.dockedStackDividerInset)
val bottomAppBounds = Region.from(0,
dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
displayBounds.right,
displayBounds.bottom - WindowUtils.navigationBarFrameHeight)
visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent())
.coversExactly(topAppBounds)
visibleRegion(Components.ImeActivity.COMPONENT.toFlickerComponent())
.coversExactly(bottomAppBounds)
}
}
@FlakyTest
@Test
fun appsEndingBounds() {
testSpec.assertLayersStart {
val displayBounds = WindowUtils.displayBounds
val dividerBounds =
layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region.bounds
val topAppBounds = Region.from(0, 0, dividerBounds.right,
dividerBounds.top + WindowUtils.dockedStackDividerInset)
val bottomAppBounds = Region.from(0,
dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
displayBounds.right,
displayBounds.bottom - WindowUtils.navigationBarFrameHeight)
visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent())
.coversExactly(topAppBounds)
visibleRegion(Components.ImeActivity.COMPONENT.toFlickerComponent())
.coversExactly(bottomAppBounds)
}
}
@Test
fun focusDoesNotChange() {
testSpec.assertEventLog {
focusDoesNotChange()
}
}
companion object {
private val startRatio = Rational(1, 3)
private val stopRatio = Rational(2, 3)
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
.getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
.map {
val description = (startRatio.toString().replace("/", "-") + "_to_" +
stopRatio.toString().replace("/", "-"))
val newName = "${FlickerTestParameter.defaultName(it)}_$description"
FlickerTestParameter(it.config, nameOverride = newName)
}
}
}
}

View File

@@ -1,114 +0,0 @@
/*
* 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.wm.shell.flicker.legacysplitscreen
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test dock activity to primary split screen and rotate
* To run this test: `atest WMShellFlickerTests:RotateOneLaunchedAppAndEnterSplitScreen`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group2
class RotateOneLaunchedAppAndEnterSplitScreen(
testSpec: FlickerTestParameter
) : LegacySplitScreenRotateTransition(testSpec) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
transitions {
device.launchSplitScreen(wmHelper)
this.setRotation(testSpec.startRotation)
}
}
@Presubmit
@Test
fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
@Presubmit
@Test
fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
splitScreenApp.component)
@Presubmit
@Test
fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@FlakyTest(bugId = 206753786)
@Test
fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Presubmit
@Test
fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@FlakyTest
@Test
fun appWindowBecomesVisible() {
testSpec.assertWm {
this.isAppWindowInvisible(splitScreenApp.component)
.then()
.isAppWindowVisible(splitScreenApp.component)
}
}
@Presubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
.getConfigNonRotationTests(repetitions = SplitScreenHelper.TEST_REPETITIONS,
supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
}
}
}

View File

@@ -1,113 +0,0 @@
/*
* 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.wm.shell.flicker.legacysplitscreen
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Rotate
* To run this test: `atest WMShellFlickerTests:RotateOneLaunchedAppInSplitScreenMode`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group2
class RotateOneLaunchedAppInSplitScreenMode(
testSpec: FlickerTestParameter
) : LegacySplitScreenRotateTransition(testSpec) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
transitions {
this.setRotation(testSpec.startRotation)
device.launchSplitScreen(wmHelper)
}
}
@Presubmit
@Test
fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
@Presubmit
@Test
fun dockedStackPrimaryBoundsIsVisibleAtEnd() = testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(
testSpec.startRotation, splitScreenApp.component)
@Presubmit
@Test
fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@FlakyTest(bugId = 206753786)
@Test
fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Presubmit
@Test
fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@FlakyTest
@Test
fun appWindowBecomesVisible() {
testSpec.assertWm {
this.isAppWindowInvisible(splitScreenApp.component)
.then()
.isAppWindowVisible(splitScreenApp.component)
}
}
@Presubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
repetitions = SplitScreenHelper.TEST_REPETITIONS,
supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
}
}
}

View File

@@ -1,136 +0,0 @@
/*
* 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.wm.shell.flicker.legacysplitscreen
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test open app to split screen.
* To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppAndEnterSplitScreen`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group2
class RotateTwoLaunchedAppAndEnterSplitScreen(
testSpec: FlickerTestParameter
) : LegacySplitScreenRotateTransition(testSpec) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
transitions {
this.setRotation(testSpec.startRotation)
device.launchSplitScreen(wmHelper)
device.reopenAppFromOverview(wmHelper)
}
}
@Presubmit
@Test
fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
@Presubmit
@Test
fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
splitScreenApp.component)
@Presubmit
@Test
fun dockedStackSecondaryBoundsIsVisibleAtEnd() =
testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.startRotation,
secondaryApp.component)
@Presubmit
@Test
fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@FlakyTest(bugId = 206753786)
@Test
fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
fun appWindowBecomesVisible() {
testSpec.assertWm {
// when the app is launched, first the activity becomes visible, then the
// SnapshotStartingWindow appears and then the app window becomes visible.
// Because we log WM once per frame, sometimes the activity and the window
// become visible in the same entry, sometimes not, thus it is not possible to
// assert the visibility of the activity here
this.isAppWindowInvisible(secondaryApp.component)
.then()
// during re-parenting, the window may disappear and reappear from the
// trace, this occurs because we log only 1x per frame
.notContains(secondaryApp.component, isOptional = true)
.then()
// if the window reappears after re-parenting it will most likely not
// be visible in the first log entry (because we log only 1x per frame)
.isAppWindowInvisible(secondaryApp.component, isOptional = true)
.then()
.isAppWindowVisible(secondaryApp.component)
}
}
@Presubmit
@Test
fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Presubmit
@Test
fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@Presubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
repetitions = SplitScreenHelper.TEST_REPETITIONS,
supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
}
}
}

View File

@@ -1,133 +0,0 @@
/*
* 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.wm.shell.flicker.legacysplitscreen
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test open app to split screen.
* To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppInSplitScreenMode`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group2
class RotateTwoLaunchedAppInSplitScreenMode(
testSpec: FlickerTestParameter
) : LegacySplitScreenRotateTransition(testSpec) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
setup {
eachRun {
device.launchSplitScreen(wmHelper)
device.reopenAppFromOverview(wmHelper)
this.setRotation(testSpec.startRotation)
}
}
transitions {
this.setRotation(testSpec.startRotation)
}
}
@Presubmit
@Test
fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
@Presubmit
@Test
fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
splitScreenApp.component)
@Presubmit
@Test
fun dockedStackSecondaryBoundsIsVisibleAtEnd() =
testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.startRotation,
secondaryApp.component)
@Presubmit
@Test
fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@FlakyTest(bugId = 206753786)
@Test
fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@FlakyTest
@Test
fun appWindowBecomesVisible() {
testSpec.assertWm {
this.isAppWindowInvisible(secondaryApp.component)
.then()
.isAppWindowVisible(secondaryApp.component)
}
}
@Presubmit
@Test
fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Presubmit
@Test
fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@Presubmit
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
repetitions = SplitScreenHelper.TEST_REPETITIONS,
supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
}
}
}

View File

@@ -1,148 +0,0 @@
/*
* 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.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import com.android.wm.shell.flicker.helpers.ImeAppHelper
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
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
/**
* Test Pip with split-screen.
* To run this test: `atest WMShellFlickerTests:PipLegacySplitScreenTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group4
class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
private val imeApp = ImeAppHelper(instrumentation)
private val testApp = FixedAppHelper(instrumentation)
@Before
open fun setup() {
// Only run legacy split tests when the system is using legacy split screen.
assumeTrue(SplitScreenHelper.isUsingLegacySplit())
// Legacy split is having some issue with Shell transition, and will be deprecated soon.
assumeFalse(isShellTransitionsEnabled())
}
override val transition: FlickerBuilder.() -> Unit
get() = {
setup {
test {
removeAllTasksButHome()
device.wakeUpAndGoToHomeScreen()
pipApp.launchViaIntent(stringExtras = mapOf(EXTRA_ENTER_PIP to "true"),
wmHelper = wmHelper)
}
}
transitions {
testApp.launchViaIntent(wmHelper)
device.launchSplitScreen(wmHelper)
imeApp.launchViaIntent(wmHelper)
}
teardown {
eachRun {
imeApp.exit(wmHelper)
testApp.exit(wmHelper)
}
test {
removeAllTasksButHome()
}
}
}
/** {@inheritDoc} */
@FlakyTest(bugId = 206753786)
@Test
override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
@FlakyTest(bugId = 161435597)
@Test
fun pipWindowInsideDisplayBounds() {
testSpec.assertWmVisibleRegion(pipApp.component) {
coversAtMost(displayBounds)
}
}
@Presubmit
@Test
fun bothAppWindowsVisible() {
testSpec.assertWmEnd {
isAppWindowVisible(testApp.component)
isAppWindowVisible(imeApp.component)
doNotOverlap(testApp.component, imeApp.component)
}
}
@FlakyTest(bugId = 161435597)
@Test
fun pipLayerInsideDisplayBounds() {
testSpec.assertLayersVisibleRegion(pipApp.component) {
coversAtMost(displayBounds)
}
}
@Presubmit
@Test
fun bothAppLayersVisible() {
testSpec.assertLayersEnd {
visibleRegion(testApp.component).coversAtMost(displayBounds)
visibleRegion(imeApp.component).coversAtMost(displayBounds)
}
}
@FlakyTest(bugId = 161435597)
@Test
override fun entireScreenCovered() = super.entireScreenCovered()
companion object {
const val TEST_REPETITIONS = 2
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
supportedRotations = listOf(Surface.ROTATION_0),
repetitions = TEST_REPETITIONS
)
}
}
}

View File

@@ -29,9 +29,9 @@ import com.android.systemui.dagger.DaggerGlobalRootComponent;
import com.android.systemui.dagger.GlobalRootComponent;
import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.dagger.WMComponent;
import com.android.wm.shell.dagger.WMShellConcurrencyModule;
import com.android.systemui.navigationbar.gestural.BackGestureTfClassifierProvider;
import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider;
import com.android.wm.shell.dagger.WMShellConcurrencyModule;
import com.android.wm.shell.transition.ShellTransitions;
import java.util.Map;
@@ -114,7 +114,6 @@ public class SystemUIFactory {
// components that shouldn't be run in the test environment
builder = prepareSysUIComponentBuilder(builder, mWMComponent)
.setPip(mWMComponent.getPip())
.setLegacySplitScreen(mWMComponent.getLegacySplitScreen())
.setSplitScreen(mWMComponent.getSplitScreen())
.setOneHanded(mWMComponent.getOneHanded())
.setBubbles(mWMComponent.getBubbles())
@@ -135,7 +134,6 @@ public class SystemUIFactory {
// is separating this logic into newly creating SystemUITestsFactory.
builder = prepareSysUIComponentBuilder(builder, mWMComponent)
.setPip(Optional.ofNullable(null))
.setLegacySplitScreen(Optional.ofNullable(null))
.setSplitScreen(Optional.ofNullable(null))
.setOneHanded(Optional.ofNullable(null))
.setBubbles(Optional.ofNullable(null))

View File

@@ -46,7 +46,6 @@ import com.android.wm.shell.compatui.CompatUI;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.draganddrop.DragAndDrop;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasks;
@@ -85,9 +84,6 @@ public interface SysUIComponent {
@BindsInstance
Builder setPip(Optional<Pip> p);
@BindsInstance
Builder setLegacySplitScreen(Optional<LegacySplitScreen> s);
@BindsInstance
Builder setSplitScreen(Optional<SplitScreen> s);

View File

@@ -37,7 +37,6 @@ import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.draganddrop.DragAndDrop;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasks;
@@ -95,9 +94,6 @@ public interface WMComponent {
@WMSingleton
Optional<Pip> getPip();
@WMSingleton
Optional<LegacySplitScreen> getLegacySplitScreen();
@WMSingleton
Optional<SplitScreen> getSplitScreen();