Update split resizing transition to match design

Remove the support of drawing side stage outline and update
OutlineManager to SplitDecorManager to help rendering decor view for
split screen like providing resizing hint.

Fix: 202740435
Fix: 202174975
Bug: 201653912
Bug: 200850654
Test: atest WMShellUnitTests
Test: manual check split resizing behavior

Merged-In: I4033255d8e2a94f69667ca1365b17da956816cde
Change-Id: I4033255d8e2a94f69667ca1365b17da956816cde
This commit is contained in:
Jerry Chang
2021-10-06 00:26:16 +08:00
parent 6d9959a09c
commit 222c9d3890
22 changed files with 395 additions and 484 deletions

View File

@@ -0,0 +1,30 @@
<!--
~ 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.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
<ImageView android:id="@+id/split_resizing_icon"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:padding="0dp"
android:visibility="gone"
android:background="@null"/>
</FrameLayout>

View File

@@ -1,18 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<!--
~ 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.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"

View File

@@ -103,8 +103,6 @@ public final class ShellCommandHandlerImpl {
return runMoveToSideStage(args, pw);
case "removeFromSideStage":
return runRemoveFromSideStage(args, pw);
case "setSideStageOutline":
return runSetSideStageOutline(args, pw);
case "setSideStagePosition":
return runSetSideStagePosition(args, pw);
case "setSideStageVisibility":
@@ -163,18 +161,6 @@ public final class ShellCommandHandlerImpl {
return true;
}
private boolean runSetSideStageOutline(String[] args, PrintWriter pw) {
if (args.length < 3) {
// First arguments are "WMShell" and command name.
pw.println("Error: whether to enable or disable side stage outline border should be"
+ " provided as arguments");
return false;
}
final boolean enable = new Boolean(args[2]);
mSplitScreenOptional.ifPresent(split -> split.setSideStageOutline(enable));
return true;
}
private boolean runSetSideStagePosition(String[] args, PrintWriter pw) {
if (args.length < 3) {
// First arguments are "WMShell" and command name.

View File

@@ -131,7 +131,8 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayou
mSplitLayout = new SplitLayout(TAG + "SplitDivider",
mDisplayController.getDisplayContext(mRootTaskInfo.displayId),
mRootTaskInfo.configuration, this /* layoutChangeListener */,
mParentContainerCallbacks, mDisplayImeController, mController.getTaskOrganizer());
mParentContainerCallbacks, mDisplayImeController, mController.getTaskOrganizer(),
true /* applyDismissingParallax */);
mDisplayInsetsController.addInsetsChangedListener(mRootTaskInfo.displayId, mSplitLayout);
final WindowContainerToken token1 = task1.token;

View File

@@ -23,16 +23,22 @@ import android.view.SurfaceSession;
* Helpers for handling surface.
*/
public class SurfaceUtils {
/** Creates a dim layer above indicated host surface. */
/** Creates a dim layer above host surface. */
public static SurfaceControl makeDimLayer(SurfaceControl.Transaction t, SurfaceControl host,
String name, SurfaceSession surfaceSession) {
SurfaceControl dimLayer = new SurfaceControl.Builder(surfaceSession)
.setParent(host)
.setColorLayer()
.setName(name)
.setCallsite("SurfaceUtils.makeDimLayer")
.build();
final SurfaceControl dimLayer = makeColorLayer(host, name, surfaceSession);
t.setLayer(dimLayer, Integer.MAX_VALUE).setColor(dimLayer, new float[]{0f, 0f, 0f});
return dimLayer;
}
/** Creates a color layer for host surface. */
public static SurfaceControl makeColorLayer(SurfaceControl host, String name,
SurfaceSession surfaceSession) {
return new SurfaceControl.Builder(surfaceSession)
.setParent(host)
.setColorLayer()
.setName(name)
.setCallsite("SurfaceUtils.makeColorLayer")
.build();
}
}

View File

@@ -0,0 +1,184 @@
/*
* 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.common.split;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
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_APPLICATION_OVERLAY;
import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Binder;
import android.view.IWindow;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceSession;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.common.SurfaceUtils;
/**
* Handles split decor like showing resizing hint for a specific split.
*/
public class SplitDecorManager extends WindowlessWindowManager {
private static final String TAG = SplitDecorManager.class.getSimpleName();
private static final String RESIZING_BACKGROUND_SURFACE_NAME = "ResizingBackground";
private final IconProvider mIconProvider;
private final SurfaceSession mSurfaceSession;
private Drawable mIcon;
private ImageView mResizingIconView;
private SurfaceControlViewHost mViewHost;
private SurfaceControl mHostLeash;
private SurfaceControl mIconLeash;
private SurfaceControl mBackgroundLeash;
public SplitDecorManager(Configuration configuration, IconProvider iconProvider,
SurfaceSession surfaceSession) {
super(configuration, null /* rootSurface */, null /* hostInputToken */);
mIconProvider = iconProvider;
mSurfaceSession = surfaceSession;
}
@Override
protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
// Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later.
final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
.setContainerLayer()
.setName(TAG)
.setHidden(true)
.setParent(mHostLeash)
.setCallsite("SplitDecorManager#attachToParentSurface");
mIconLeash = builder.build();
b.setParent(mIconLeash);
}
/** Inflates split decor surface on the root surface. */
public void inflate(Context context, SurfaceControl rootLeash, Rect rootBounds) {
if (mIconLeash != null && mViewHost != null) {
return;
}
context = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY,
null /* options */);
mHostLeash = rootLeash;
mViewHost = new SurfaceControlViewHost(context, context.getDisplay(), this);
final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(context)
.inflate(R.layout.split_decor, null);
mResizingIconView = rootLayout.findViewById(R.id.split_resizing_icon);
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY,
FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
lp.width = rootBounds.width();
lp.height = rootBounds.height();
lp.token = new Binder();
lp.setTitle(TAG);
lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
// TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports
// TRUSTED_OVERLAY for windowless window without input channel.
mViewHost.setView(rootLayout, lp);
}
/** Releases the surfaces for split decor. */
public void release(SurfaceControl.Transaction t) {
if (mViewHost != null) {
mViewHost.release();
mViewHost = null;
}
if (mIconLeash != null) {
t.remove(mIconLeash);
mIconLeash = null;
}
if (mBackgroundLeash != null) {
t.remove(mBackgroundLeash);
mBackgroundLeash = null;
}
mHostLeash = null;
mIcon = null;
mResizingIconView = null;
}
/** Showing resizing hint. */
public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
SurfaceControl.Transaction t) {
if (mResizingIconView == null) {
return;
}
if (mIcon == null) {
// TODO: add fade-in animation.
mBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
RESIZING_BACKGROUND_SURFACE_NAME, mSurfaceSession);
t.setColor(mBackgroundLeash, getResizingBackgroundColor(resizingTask))
.setLayer(mBackgroundLeash, SPLIT_DIVIDER_LAYER - 1)
.show(mBackgroundLeash);
mIcon = mIconProvider.getIcon(resizingTask.topActivityInfo);
mResizingIconView.setImageDrawable(mIcon);
mResizingIconView.setVisibility(View.VISIBLE);
WindowManager.LayoutParams lp =
(WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
lp.width = mIcon.getIntrinsicWidth();
lp.height = mIcon.getIntrinsicHeight();
mViewHost.relayout(lp);
t.show(mIconLeash).setLayer(mIconLeash, SPLIT_DIVIDER_LAYER);
}
t.setPosition(mIconLeash,
newBounds.width() / 2 - mIcon.getIntrinsicWidth() / 2,
newBounds.height() / 2 - mIcon.getIntrinsicWidth() / 2);
}
/** Stops showing resizing hint. */
public void onResized(Rect newBounds, SurfaceControl.Transaction t) {
if (mResizingIconView == null) {
return;
}
if (mIcon != null) {
mResizingIconView.setVisibility(View.GONE);
mResizingIconView.setImageDrawable(null);
t.remove(mBackgroundLeash).hide(mIconLeash);
mIcon = null;
mBackgroundLeash = null;
}
}
private static float[] getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) {
final int taskBgColor = taskInfo.taskDescription.getBackgroundColor();
return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).getComponents();
}
}

View File

@@ -103,7 +103,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
private final SplitWindowManager mSplitWindowManager;
private final DisplayImeController mDisplayImeController;
private final ImePositionProcessor mImePositionProcessor;
private final DismissingParallaxPolicy mDismissingParallaxPolicy;
private final DismissingEffectPolicy mDismissingEffectPolicy;
private final ShellTaskOrganizer mTaskOrganizer;
private final InsetsState mInsetsState = new InsetsState();
@@ -119,7 +119,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
public SplitLayout(String windowName, Context context, Configuration configuration,
SplitLayoutHandler splitLayoutHandler,
SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks,
DisplayImeController displayImeController, ShellTaskOrganizer taskOrganizer) {
DisplayImeController displayImeController, ShellTaskOrganizer taskOrganizer,
boolean applyDismissingParallax) {
mContext = context.createConfigurationContext(configuration);
mOrientation = configuration.orientation;
mRotation = configuration.windowConfiguration.getRotation();
@@ -129,7 +130,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
parentContainerCallbacks);
mTaskOrganizer = taskOrganizer;
mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId());
mDismissingParallaxPolicy = new DismissingParallaxPolicy();
mDismissingEffectPolicy = new DismissingEffectPolicy(applyDismissingParallax);
final Resources resources = context.getResources();
mDividerWindowWidth = resources.getDimensionPixelSize(
@@ -247,7 +248,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
DockedDividerUtils.sanitizeStackBounds(mBounds1, true /** topLeft */);
DockedDividerUtils.sanitizeStackBounds(mBounds2, false /** topLeft */);
mDismissingParallaxPolicy.applyDividerPosition(position, isLandscape);
mDismissingEffectPolicy.applyDividerPosition(position, isLandscape);
}
/** Inflates {@link DividerView} on the root surface. */
@@ -290,7 +291,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
*/
void updateDivideBounds(int position) {
updateBounds(position);
mSplitWindowManager.setResizingSplits(true);
mSplitLayoutHandler.onLayoutSizeChanging(this);
}
@@ -298,13 +298,11 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mDividePosition = position;
updateBounds(mDividePosition);
mSplitLayoutHandler.onLayoutSizeChanged(this);
mSplitWindowManager.setResizingSplits(false);
}
/** Resets divider position. */
public void resetDividerPosition() {
mDividePosition = mDividerSnapAlgorithm.getMiddleTarget().position;
mSplitWindowManager.setResizingSplits(false);
updateBounds(mDividePosition);
mWinToken1 = null;
mWinToken2 = null;
@@ -360,8 +358,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
@VisibleForTesting
void flingDividePosition(int from, int to, @Nullable Runnable flingFinishedCallback) {
if (from == to) {
// No animation run, it should stop resizing here.
mSplitWindowManager.setResizingSplits(false);
// No animation run, still callback to stop resizing.
mSplitLayoutHandler.onLayoutSizeChanged(this);
return;
}
ValueAnimator animator = ValueAnimator
@@ -425,7 +423,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
return;
}
mDismissingParallaxPolicy.adjustDismissingSurface(t, leash1, leash2, dimLayer1, dimLayer2);
mDismissingEffectPolicy.adjustDismissingSurface(t, leash1, leash2, dimLayer1, dimLayer2);
}
/** Apply recorded task layout to the {@link WindowContainerTransaction}. */
@@ -543,7 +541,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
* Calculates and applies proper dismissing parallax offset and dimming value to hint users
* dismissing gesture.
*/
private class DismissingParallaxPolicy {
private class DismissingEffectPolicy {
/** Indicates whether to offset splitting bounds to hint dismissing progress or not. */
private final boolean mApplyParallax;
// The current dismissing side.
int mDismissingSide = DOCKED_INVALID;
@@ -553,6 +554,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
// The dimming value to hint the dismissing side and progress.
float mDismissingDimValue = 0.0f;
DismissingEffectPolicy(boolean applyDismissingParallax) {
mApplyParallax = applyDismissingParallax;
}
/**
* Applies a parallax to the task to hint dismissing progress.
*
@@ -627,12 +632,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
return false;
}
t.setPosition(targetLeash,
mTempRect.left + mDismissingParallaxOffset.x,
mTempRect.top + mDismissingParallaxOffset.y);
// Transform the screen-based split bounds to surface-based crop bounds.
mTempRect.offsetTo(-mDismissingParallaxOffset.x, -mDismissingParallaxOffset.y);
t.setWindowCrop(targetLeash, mTempRect);
if (mApplyParallax) {
t.setPosition(targetLeash,
mTempRect.left + mDismissingParallaxOffset.x,
mTempRect.top + mDismissingParallaxOffset.y);
// Transform the screen-based split bounds to surface-based crop bounds.
mTempRect.offsetTo(-mDismissingParallaxOffset.x, -mDismissingParallaxOffset.y);
t.setWindowCrop(targetLeash, mTempRect);
}
t.setAlpha(targetDimLayer, mDismissingDimValue)
.setVisibility(targetDimLayer, mDismissingDimValue > 0.001f);
return true;

View File

@@ -25,7 +25,6 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMA
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
@@ -33,8 +32,6 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import android.view.IWindow;
import android.view.InsetsState;
import android.view.LayoutInflater;
@@ -59,7 +56,6 @@ public final class SplitWindowManager extends WindowlessWindowManager {
private Context mContext;
private SurfaceControlViewHost mViewHost;
private SurfaceControl mLeash;
private boolean mResizingSplits;
private DividerView mDividerView;
public interface ParentContainerCallbacks {
@@ -154,16 +150,6 @@ public final class SplitWindowManager extends WindowlessWindowManager {
mDividerView.setInteractive(interactive);
}
void setResizingSplits(boolean resizing) {
if (resizing == mResizingSplits) return;
try {
ActivityTaskManager.getService().setSplitScreenResizing(resizing);
mResizingSplits = resizing;
} catch (RemoteException e) {
Slog.w(TAG, "Error calling setSplitScreenResizing", e);
}
}
/**
* Gets {@link SurfaceControl} of the surface holding divider view. @return {@code null} if not
* feasible.

View File

@@ -19,11 +19,13 @@ package com.android.wm.shell.splitscreen;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -37,11 +39,11 @@ class MainStage extends StageTaskListener {
private boolean mIsActive = false;
MainStage(ShellTaskOrganizer taskOrganizer, int displayId,
MainStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
SurfaceSession surfaceSession,
SurfaceSession surfaceSession, IconProvider iconProvider,
@Nullable StageTaskUnfoldController stageTaskUnfoldController) {
super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, iconProvider,
stageTaskUnfoldController);
}

View File

@@ -1,181 +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.splitscreen;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
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_APPLICATION_OVERLAY;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Binder;
import android.view.IWindow;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
import android.widget.FrameLayout;
import com.android.wm.shell.R;
/**
* Handles drawing outline of the bounds of provided root surface. The outline will be drown with
* the consideration of display insets like status bar, navigation bar and display cutout.
*/
class OutlineManager extends WindowlessWindowManager {
private static final String WINDOW_NAME = "SplitOutlineLayer";
private final Context mContext;
private final Rect mRootBounds = new Rect();
private final Rect mTempRect = new Rect();
private final Rect mLastOutlineBounds = new Rect();
private final InsetsState mInsetsState = new InsetsState();
private final int mExpandedTaskBarHeight;
private OutlineView mOutlineView;
private SurfaceControlViewHost mViewHost;
private SurfaceControl mHostLeash;
private SurfaceControl mLeash;
OutlineManager(Context context, Configuration configuration) {
super(configuration, null /* rootSurface */, null /* hostInputToken */);
mContext = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY,
null /* options */);
mExpandedTaskBarHeight = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.taskbar_frame_height);
}
@Override
protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
b.setParent(mHostLeash);
}
void inflate(SurfaceControl rootLeash, Rect rootBounds) {
if (mLeash != null || mViewHost != null) return;
mHostLeash = rootLeash;
mRootBounds.set(rootBounds);
mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(mContext)
.inflate(R.layout.split_outline, null);
mOutlineView = rootLayout.findViewById(R.id.split_outline);
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY,
FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
lp.width = mRootBounds.width();
lp.height = mRootBounds.height();
lp.token = new Binder();
lp.setTitle(WINDOW_NAME);
lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
// TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports
// TRUSTED_OVERLAY for windowless window without input channel.
mViewHost.setView(rootLayout, lp);
mLeash = getSurfaceControl(mViewHost.getWindowToken());
drawOutline();
}
void release() {
if (mViewHost != null) {
mViewHost.release();
mViewHost = null;
}
mRootBounds.setEmpty();
mLastOutlineBounds.setEmpty();
mOutlineView = null;
mHostLeash = null;
mLeash = null;
}
@Nullable
SurfaceControl getOutlineLeash() {
return mLeash;
}
void setVisibility(boolean visible) {
if (mOutlineView != null) {
mOutlineView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
}
}
void setRootBounds(Rect rootBounds) {
if (mViewHost == null || mViewHost.getView() == null) {
return;
}
if (!mRootBounds.equals(rootBounds)) {
WindowManager.LayoutParams lp =
(WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
lp.width = rootBounds.width();
lp.height = rootBounds.height();
mViewHost.relayout(lp);
mRootBounds.set(rootBounds);
drawOutline();
}
}
void onInsetsChanged(InsetsState insetsState) {
if (!mInsetsState.equals(insetsState)) {
mInsetsState.set(insetsState);
drawOutline();
}
}
private void computeOutlineBounds(Rect rootBounds, InsetsState insetsState, Rect outBounds) {
outBounds.set(rootBounds);
final InsetsSource taskBarInsetsSource =
insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
// Only insets the divider bar with task bar when it's expanded so that the rounded corners
// will be drawn against task bar.
if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
outBounds.inset(taskBarInsetsSource.calculateVisibleInsets(outBounds));
}
// Offset the coordinate from screen based to surface based.
outBounds.offset(-rootBounds.left, -rootBounds.top);
}
void drawOutline() {
if (mOutlineView == null) {
return;
}
computeOutlineBounds(mRootBounds, mInsetsState, mTempRect);
if (mTempRect.equals(mLastOutlineBounds)) {
return;
}
ViewGroup.MarginLayoutParams lp =
(ViewGroup.MarginLayoutParams) mOutlineView.getLayoutParams();
lp.leftMargin = mTempRect.left;
lp.topMargin = mTempRect.top;
lp.width = mTempRect.width();
lp.height = mTempRect.height();
mOutlineView.setLayoutParams(lp);
mLastOutlineBounds.set(mTempRect);
}
}

View File

@@ -1,82 +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.splitscreen;
import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
import static android.view.RoundedCorner.POSITION_TOP_LEFT;
import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.RoundedCorner;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.R;
/** View for drawing split outline. */
public class OutlineView extends View {
private final Paint mPaint = new Paint();
private final Path mPath = new Path();
private final float[] mRadii = new float[8];
public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(
getResources().getDimension(R.dimen.accessibility_focus_highlight_stroke_width));
mPaint.setColor(getResources().getColor(R.color.system_accent1_100, null));
}
@Override
protected void onAttachedToWindow() {
// TODO(b/200850654): match the screen corners with the actual display decor.
mRadii[0] = mRadii[1] = getCornerRadius(POSITION_TOP_LEFT);
mRadii[2] = mRadii[3] = getCornerRadius(POSITION_TOP_RIGHT);
mRadii[4] = mRadii[5] = getCornerRadius(POSITION_BOTTOM_RIGHT);
mRadii[6] = mRadii[7] = getCornerRadius(POSITION_BOTTOM_LEFT);
}
private int getCornerRadius(@RoundedCorner.Position int position) {
final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(position);
return roundedCorner == null ? 0 : roundedCorner.getRadius();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (changed) {
mPath.reset();
mPath.addRoundRect(0, 0, getWidth(), getHeight(), mRadii, Path.Direction.CW);
}
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(mPath, mPaint);
}
@Override
public boolean hasOverlappingRendering() {
return false;
}
}

View File

@@ -16,20 +16,16 @@
package com.android.wm.shell.splitscreen;
import android.annotation.CallSuper;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Rect;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.SyncTransactionQueue;
/**
@@ -38,19 +34,15 @@ import com.android.wm.shell.common.SyncTransactionQueue;
*
* @see StageCoordinator
*/
class SideStage extends StageTaskListener implements
DisplayInsetsController.OnInsetsChangedListener {
class SideStage extends StageTaskListener {
private static final String TAG = SideStage.class.getSimpleName();
private final Context mContext;
private OutlineManager mOutlineManager;
SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
SurfaceSession surfaceSession,
SurfaceSession surfaceSession, IconProvider iconProvider,
@Nullable StageTaskUnfoldController stageTaskUnfoldController) {
super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, iconProvider,
stageTaskUnfoldController);
mContext = context;
}
void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds,
@@ -83,62 +75,4 @@ class SideStage extends StageTaskListener implements
wct.reparent(task.token, newParent, false /* onTop */);
return true;
}
@Nullable
public SurfaceControl getOutlineLeash() {
return mOutlineManager.getOutlineLeash();
}
@Override
@CallSuper
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
super.onTaskAppeared(taskInfo, leash);
if (isRootTask(taskInfo)) {
mOutlineManager = new OutlineManager(mContext, taskInfo.configuration);
enableOutline(true);
}
}
@Override
@CallSuper
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
super.onTaskInfoChanged(taskInfo);
if (isRootTask(taskInfo)) {
mOutlineManager.setRootBounds(taskInfo.configuration.windowConfiguration.getBounds());
}
}
private boolean isRootTask(ActivityManager.RunningTaskInfo taskInfo) {
return mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId;
}
void enableOutline(boolean enable) {
if (mOutlineManager == null) {
return;
}
if (enable) {
if (mRootTaskInfo != null) {
mOutlineManager.inflate(mRootLeash,
mRootTaskInfo.configuration.windowConfiguration.getBounds());
}
} else {
mOutlineManager.release();
}
}
void setOutlineVisibility(boolean visible) {
mOutlineManager.setVisibility(visible);
}
@Override
public void insetsChanged(InsetsState insetsState) {
mOutlineManager.onInsetsChanged(insetsState);
}
@Override
public void insetsControlChanged(InsetsState insetsState,
InsetsSourceControl[] activeControls) {
insetsChanged(insetsState);
}
}

View File

@@ -54,6 +54,7 @@ import androidx.annotation.Nullable;
import com.android.internal.logging.InstanceId;
import com.android.internal.util.FrameworkStatsLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayImeController;
@@ -78,6 +79,7 @@ import javax.inject.Provider;
/**
* Class manages split-screen multitasking mode and implements the main interface
* {@link SplitScreen}.
*
* @see StageCoordinator
*/
// TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen.
@@ -96,6 +98,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private final Transitions mTransitions;
private final TransactionPool mTransactionPool;
private final SplitscreenEventLogger mLogger;
private final IconProvider mIconProvider;
private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;
private StageCoordinator mStageCoordinator;
@@ -105,7 +108,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
RootTaskDisplayAreaOrganizer rootTDAOrganizer,
ShellExecutor mainExecutor, DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
Transitions transitions, TransactionPool transactionPool,
Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider,
Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
@@ -118,6 +121,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mTransactionPool = transactionPool;
mUnfoldControllerProvider = unfoldControllerProvider;
mLogger = new SplitscreenEventLogger();
mIconProvider = iconProvider;
}
public SplitScreen asSplitScreen() {
@@ -140,7 +144,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,
mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
mUnfoldControllerProvider);
mIconProvider, mUnfoldControllerProvider);
}
}
@@ -165,10 +169,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return mStageCoordinator.removeFromSideStage(taskId);
}
public void setSideStageOutline(boolean enable) {
mStageCoordinator.setSideStageOutline(enable);
}
public void setSideStagePosition(@SplitPosition int sideStagePosition) {
mStageCoordinator.setSideStagePosition(sideStagePosition, null /* wct */);
}
@@ -319,9 +319,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
transaction.apply();
transaction.close();
return new RemoteAnimationTarget[]{
mStageCoordinator.getDividerBarLegacyTarget(),
mStageCoordinator.getOutlineLegacyTarget()};
return new RemoteAnimationTarget[]{mStageCoordinator.getDividerBarLegacyTarget()};
}
/**

View File

@@ -80,6 +80,7 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayImeController;
@@ -151,10 +152,12 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Whether the device is supporting legacy split or not. */
private boolean mUseLegacySplit;
@SplitScreen.StageType private int mDismissTop = NO_DISMISS;
@SplitScreen.StageType
private int mDismissTop = NO_DISMISS;
/** The target stage to dismiss to when unlock after folded. */
@SplitScreen.StageType private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
@SplitScreen.StageType
private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
private final Runnable mOnTransitionAnimationComplete = () -> {
// If still playing, let it finish.
@@ -169,22 +172,23 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
new SplitWindowManager.ParentContainerCallbacks() {
@Override
public void attachToParentSurface(SurfaceControl.Builder b) {
mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b);
}
@Override
public void attachToParentSurface(SurfaceControl.Builder b) {
mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b);
}
@Override
public void onLeashReady(SurfaceControl leash) {
mSyncQueue.runInSync(t -> applyDividerVisibility(t));
}
};
@Override
public void onLeashReady(SurfaceControl leash) {
mSyncQueue.runInSync(t -> applyDividerVisibility(t));
}
};
StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController, Transitions transitions,
TransactionPool transactionPool, SplitscreenEventLogger logger,
IconProvider iconProvider,
Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
mContext = context;
mDisplayId = displayId;
@@ -196,11 +200,13 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
mMainStage = new MainStage(
mContext,
mTaskOrganizer,
mDisplayId,
mMainStageListener,
mSyncQueue,
mSurfaceSession,
iconProvider,
mMainUnfoldController);
mSideStage = new SideStage(
mContext,
@@ -209,10 +215,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStageListener,
mSyncQueue,
mSurfaceSession,
iconProvider,
mSideUnfoldController);
mDisplayImeController = displayImeController;
mDisplayInsetsController = displayInsetsController;
mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSideStage);
mRootTDAOrganizer.registerListener(displayId, this);
final DeviceStateManager deviceStateManager =
mContext.getSystemService(DeviceStateManager.class);
@@ -284,10 +290,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return result;
}
void setSideStageOutline(boolean enable) {
mSideStage.enableOutline(enable);
}
/** Starts 2 tasks in one transition. */
void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
@Nullable Bundle sideOptions, @SplitPosition int sidePosition,
@@ -697,9 +699,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (bothStageInvisible) {
if (mExitSplitScreenOnHide
// Don't dismiss staged split when both stages are not visible due to sleeping display,
// like the cases keyguard showing or screen off.
|| (!mMainStage.mRootTaskInfo.isSleeping && !mSideStage.mRootTaskInfo.isSleeping)) {
// Don't dismiss staged split when both stages are not visible due to sleeping
// display, like the cases keyguard showing or screen off.
|| (!mMainStage.mRootTaskInfo.isSleeping
&& !mSideStage.mRootTaskInfo.isSleeping)) {
exitSplitScreen(null /* childrenToTop */,
SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME);
}
@@ -719,7 +722,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
t.setVisibility(mSideStage.mRootLeash, bothStageVisible)
.setVisibility(mMainStage.mRootLeash, bothStageVisible);
applyDividerVisibility(t);
applyOutlineVisibility(t);
}
});
}
@@ -741,19 +743,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
private void applyOutlineVisibility(SurfaceControl.Transaction t) {
final SurfaceControl outlineLeash = mSideStage.getOutlineLeash();
if (outlineLeash == null) {
return;
}
if (mDividerVisible) {
t.show(outlineLeash).setLayer(outlineLeash, SPLIT_DIVIDER_LAYER);
} else {
t.hide(outlineLeash);
}
}
private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
final boolean hasChildren = stageListener.mHasChildren;
final boolean isSideStage = stageListener == mSideStageListener;
@@ -817,8 +806,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onLayoutSizeChanging(SplitLayout layout) {
mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
mSideStage.setOutlineVisibility(false);
mSyncQueue.runInSync(t -> {
updateSurfaceBounds(layout, t);
mMainStage.onResizing(getMainStageBounds(), t);
mSideStage.onResizing(getSideStageBounds(), t);
});
}
@Override
@@ -827,8 +819,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
updateWindowBounds(layout, wct);
updateUnfoldBounds();
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
mSideStage.setOutlineVisibility(true);
mSyncQueue.runInSync(t -> {
updateSurfaceBounds(layout, t);
mMainStage.onResized(getMainStageBounds(), t);
mSideStage.onResized(getSideStageBounds(), t);
});
mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
}
@@ -893,7 +888,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mSplitLayout == null) {
mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
mDisplayAreaInfo.configuration, this, mParentContainerCallbacks,
mDisplayImeController, mTaskOrganizer);
mDisplayImeController, mTaskOrganizer, false /* applyDismissingParallax */);
mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
if (mMainUnfoldController != null && mSideUnfoldController != null) {
@@ -1216,18 +1211,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
}
RemoteAnimationTarget getOutlineLegacyTarget() {
final Rect bounds = mSideStage.mRootTaskInfo.configuration.windowConfiguration.getBounds();
// Leverage TYPE_DOCK_DIVIDER type when wrapping outline remote animation target in order to
// distinguish as a split auxiliary target in Launcher.
return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
mSideStage.getOutlineLeash(), false /* isTranslucent */, null /* clipRect */,
null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
new android.graphics.Point(0, 0) /* position */, bounds, bounds,
new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */,
null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
}
@Override
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";

View File

@@ -26,6 +26,7 @@ import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIO
import android.annotation.CallSuper;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.SparseArray;
@@ -35,9 +36,11 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import com.android.launcher3.icons.IconProvider;
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.common.split.SplitDecorManager;
import java.io.PrintWriter;
@@ -72,25 +75,31 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
void onNoLongerSupportMultiWindow();
}
private final Context mContext;
private final StageListenerCallbacks mCallbacks;
private final SurfaceSession mSurfaceSession;
protected final SyncTransactionQueue mSyncQueue;
private final SyncTransactionQueue mSyncQueue;
private final IconProvider mIconProvider;
protected ActivityManager.RunningTaskInfo mRootTaskInfo;
protected SurfaceControl mRootLeash;
protected SurfaceControl mDimLayer;
protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>();
private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>();
// TODO(b/204308910): Extracts SplitDecorManager related code to common package.
private SplitDecorManager mSplitDecorManager;
private final StageTaskUnfoldController mStageTaskUnfoldController;
StageTaskListener(ShellTaskOrganizer taskOrganizer, int displayId,
StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
SurfaceSession surfaceSession,
SurfaceSession surfaceSession, IconProvider iconProvider,
@Nullable StageTaskUnfoldController stageTaskUnfoldController) {
mContext = context;
mCallbacks = callbacks;
mSyncQueue = syncQueue;
mSurfaceSession = surfaceSession;
mIconProvider = iconProvider;
mStageTaskUnfoldController = stageTaskUnfoldController;
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
}
@@ -142,13 +151,14 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
if (mRootTaskInfo == null && !taskInfo.hasParentTask()) {
mRootLeash = leash;
mRootTaskInfo = taskInfo;
mSplitDecorManager = new SplitDecorManager(
mRootTaskInfo.configuration,
mIconProvider,
mSurfaceSession);
mCallbacks.onRootTaskAppeared();
sendStatusChanged();
mSyncQueue.runInSync(t -> {
t.hide(mRootLeash);
mDimLayer =
SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession);
});
mSyncQueue.runInSync(t -> mDimLayer =
SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession));
} else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
final int taskId = taskInfo.taskId;
mChildrenLeashes.put(taskId, leash);
@@ -179,6 +189,17 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
return;
}
if (mRootTaskInfo.taskId == taskInfo.taskId) {
// Inflates split decor view only when the root task is visible.
if (mRootTaskInfo.isVisible != taskInfo.isVisible) {
mSyncQueue.runInSync(t -> {
if (taskInfo.isVisible) {
mSplitDecorManager.inflate(mContext, mRootLeash,
taskInfo.configuration.windowConfiguration.getBounds());
} else {
mSplitDecorManager.release(t);
}
});
}
mRootTaskInfo = taskInfo;
} else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
@@ -205,8 +226,11 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
final int taskId = taskInfo.taskId;
if (mRootTaskInfo.taskId == taskId) {
mCallbacks.onRootTaskVanished();
mSyncQueue.runInSync(t -> t.remove(mDimLayer));
mRootTaskInfo = null;
mSyncQueue.runInSync(t -> {
t.remove(mDimLayer);
mSplitDecorManager.release(t);
});
} else if (mChildrenTaskInfo.contains(taskId)) {
mChildrenTaskInfo.remove(taskId);
mChildrenLeashes.remove(taskId);
@@ -237,6 +261,18 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
}
void onResizing(Rect newBounds, SurfaceControl.Transaction t) {
if (mSplitDecorManager != null && mRootTaskInfo != null) {
mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, t);
}
}
void onResized(Rect newBounds, SurfaceControl.Transaction t) {
if (mSplitDecorManager != null) {
mSplitDecorManager.onResized(newBounds, t);
}
}
void setBounds(Rect bounds, WindowContainerTransaction wct) {
wct.setBounds(mRootTaskInfo.token, bounds);
}

View File

@@ -882,7 +882,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mSplitLayout == null) {
mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
mDisplayAreaInfo.configuration, this, mParentContainerCallbacks,
mDisplayImeController, mTaskOrganizer);
mDisplayImeController, mTaskOrganizer, true /* applyDismissingParallax */);
mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
if (mMainUnfoldController != null && mSideUnfoldController != null) {

View File

@@ -68,7 +68,8 @@ public class SplitLayoutTests extends ShellTestCase {
mSplitLayoutHandler,
mCallbacks,
mDisplayImeController,
mTaskOrganizer));
mTaskOrganizer,
false /* applyDismissingParallax */));
}
@Test

View File

@@ -25,10 +25,13 @@ import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.WindowContainerTransaction;
import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -41,22 +44,24 @@ import org.mockito.MockitoAnnotations;
/** Tests for {@link MainStage} */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class MainStageTests {
public class MainStageTests extends ShellTestCase {
@Mock private ShellTaskOrganizer mTaskOrganizer;
@Mock private StageTaskListener.StageListenerCallbacks mCallbacks;
@Mock private SyncTransactionQueue mSyncQueue;
@Mock private ActivityManager.RunningTaskInfo mRootTaskInfo;
@Mock private SurfaceControl mRootLeash;
@Mock private IconProvider mIconProvider;
private WindowContainerTransaction mWct = new WindowContainerTransaction();
private SurfaceSession mSurfaceSession = new SurfaceSession();
private MainStage mMainStage;
@Before
@UiThreadTest
public void setup() {
MockitoAnnotations.initMocks(this);
mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, mSyncQueue,
mSurfaceSession, null);
mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
mSyncQueue, mSurfaceSession, mIconProvider, null);
mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash);
}

View File

@@ -33,6 +33,7 @@ import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
@@ -54,6 +55,7 @@ public class SideStageTests extends ShellTestCase {
@Mock private SyncTransactionQueue mSyncQueue;
@Mock private ActivityManager.RunningTaskInfo mRootTask;
@Mock private SurfaceControl mRootLeash;
@Mock private IconProvider mIconProvider;
@Spy private WindowContainerTransaction mWct;
private SurfaceSession mSurfaceSession = new SurfaceSession();
private SideStage mSideStage;
@@ -64,7 +66,7 @@ public class SideStageTests extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mRootTask = new TestRunningTaskInfoBuilder().build();
mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
mSyncQueue, mSurfaceSession, null);
mSyncQueue, mSurfaceSession, mIconProvider, null);
mSideStage.onTaskAppeared(mRootTask, mRootLeash);
}

View File

@@ -55,6 +55,7 @@ import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
@@ -89,6 +90,7 @@ public class SplitTransitionTests extends ShellTestCase {
@Mock private Transitions mTransitions;
@Mock private SurfaceSession mSurfaceSession;
@Mock private SplitscreenEventLogger mLogger;
@Mock private IconProvider mIconProvider;
private SplitLayout mSplitLayout;
private MainStage mMainStage;
private SideStage mSideStage;
@@ -107,11 +109,13 @@ public class SplitTransitionTests extends ShellTestCase {
doReturn(mockExecutor).when(mTransitions).getAnimExecutor();
doReturn(mock(SurfaceControl.Transaction.class)).when(mTransactionPool).acquire();
mSplitLayout = SplitTestUtils.createMockSplitLayout();
mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mock(
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, null);
mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
mIconProvider, null);
mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, null);
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
mIconProvider, null);
mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,

View File

@@ -35,10 +35,13 @@ import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.WindowContainerTransaction;
import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -57,7 +60,7 @@ import org.mockito.MockitoAnnotations;
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public final class StageTaskListenerTests {
public final class StageTaskListenerTests extends ShellTestCase {
private static final boolean ENABLE_SHELL_TRANSITIONS =
SystemProperties.getBoolean("persist.debug.shell_transit", false);
@@ -68,6 +71,8 @@ public final class StageTaskListenerTests {
@Mock
private SyncTransactionQueue mSyncQueue;
@Mock
private IconProvider mIconProvider;
@Mock
private StageTaskUnfoldController mStageTaskUnfoldController;
@Captor
private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor;
@@ -77,14 +82,17 @@ public final class StageTaskListenerTests {
private StageTaskListener mStageTaskListener;
@Before
@UiThreadTest
public void setup() {
MockitoAnnotations.initMocks(this);
mStageTaskListener = new StageTaskListener(
mContext,
mTaskOrganizer,
DEFAULT_DISPLAY,
mCallbacks,
mSyncQueue,
mSurfaceSession,
mIconProvider,
mStageTaskUnfoldController);
mRootTask = new TestRunningTaskInfoBuilder().build();
mRootTask.parentTaskId = INVALID_TASK_ID;

View File

@@ -463,13 +463,13 @@ public abstract class WMShellBaseModule {
@ShellMainThread ShellExecutor mainExecutor,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController, Transitions transitions,
TransactionPool transactionPool,
TransactionPool transactionPool, IconProvider iconProvider,
Provider<Optional<StageTaskUnfoldController>> stageTaskUnfoldControllerProvider) {
if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
return Optional.of(new SplitScreenController(shellTaskOrganizer, syncQueue, context,
rootTaskDisplayAreaOrganizer, mainExecutor, displayImeController,
displayInsetsController, transitions,
transactionPool, stageTaskUnfoldControllerProvider));
displayInsetsController, transitions, transactionPool, iconProvider,
stageTaskUnfoldControllerProvider));
} else {
return Optional.empty();
}