Clean up the legacy split screen (9/n)
This should be final clean patch, remove legacy split screen package and its windowing modes Bug: 199236198 Test: build pass Test: pass existing tests Ignore-AOSP-First: remove whole package Change-Id: Ib2af42834938032bf525b8c16b1046b4c898b8d5
This commit is contained in:
@@ -484,8 +484,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
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
# window manager > wm shell > Split Screen
|
||||
# Bug component: 928697
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user