Merge "Consolidate the behavior and style of side stage outline" into sc-v2-dev
This commit is contained in:
@@ -13,7 +13,7 @@
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<com.android.wm.shell.splitscreen.OutlineRoot
|
<FrameLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
@@ -23,4 +23,4 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_width="match_parent" />
|
android:layout_width="match_parent" />
|
||||||
|
|
||||||
</com.android.wm.shell.splitscreen.OutlineRoot>
|
</FrameLayout>
|
||||||
|
|||||||
@@ -22,19 +22,23 @@ 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.PRIVATE_FLAG_TRUSTED_OVERLAY;
|
||||||
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||||
|
|
||||||
|
import android.annotation.Nullable;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.PixelFormat;
|
import android.graphics.PixelFormat;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.view.IWindow;
|
import android.view.IWindow;
|
||||||
|
import android.view.InsetsSource;
|
||||||
|
import android.view.InsetsState;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.SurfaceControl;
|
import android.view.SurfaceControl;
|
||||||
import android.view.SurfaceControlViewHost;
|
import android.view.SurfaceControlViewHost;
|
||||||
import android.view.WindowInsets;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.WindowMetrics;
|
|
||||||
import android.view.WindowlessWindowManager;
|
import android.view.WindowlessWindowManager;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
import com.android.wm.shell.R;
|
import com.android.wm.shell.R;
|
||||||
|
|
||||||
@@ -45,17 +49,22 @@ import com.android.wm.shell.R;
|
|||||||
class OutlineManager extends WindowlessWindowManager {
|
class OutlineManager extends WindowlessWindowManager {
|
||||||
private static final String WINDOW_NAME = "SplitOutlineLayer";
|
private static final String WINDOW_NAME = "SplitOutlineLayer";
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final Rect mOutlineBounds = new Rect();
|
private final Rect mRootBounds = new Rect();
|
||||||
private final Rect mTmpBounds = 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 SurfaceControlViewHost mViewHost;
|
||||||
private SurfaceControl mHostLeash;
|
private SurfaceControl mHostLeash;
|
||||||
private SurfaceControl mLeash;
|
private SurfaceControl mLeash;
|
||||||
private int mOutlineColor;
|
|
||||||
|
|
||||||
OutlineManager(Context context, Configuration configuration) {
|
OutlineManager(Context context, Configuration configuration) {
|
||||||
super(configuration, null /* rootSurface */, null /* hostInputToken */);
|
super(configuration, null /* rootSurface */, null /* hostInputToken */);
|
||||||
mContext = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY,
|
mContext = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY,
|
||||||
null /* options */);
|
null /* options */);
|
||||||
|
mExpandedTaskBarHeight = mContext.getResources().getDimensionPixelSize(
|
||||||
|
com.android.internal.R.dimen.taskbar_frame_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -63,65 +72,110 @@ class OutlineManager extends WindowlessWindowManager {
|
|||||||
b.setParent(mHostLeash);
|
b.setParent(mHostLeash);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean drawOutlineBounds(Rect rootBounds) {
|
void inflate(SurfaceControl rootLeash, Rect rootBounds) {
|
||||||
if (mLeash == null || mViewHost == null) return false;
|
|
||||||
|
|
||||||
computeOutlineBounds(mContext, rootBounds, mTmpBounds);
|
|
||||||
if (mOutlineBounds.equals(mTmpBounds)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
mOutlineBounds.set(mTmpBounds);
|
|
||||||
|
|
||||||
((OutlineRoot) mViewHost.getView()).updateOutlineBounds(mOutlineBounds, mOutlineColor);
|
|
||||||
final WindowManager.LayoutParams lp =
|
|
||||||
(WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
|
|
||||||
lp.width = rootBounds.width();
|
|
||||||
lp.height = rootBounds.height();
|
|
||||||
mViewHost.relayout(lp);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void inflate(SurfaceControl.Transaction t, SurfaceControl hostLeash, int color) {
|
|
||||||
if (mLeash != null || mViewHost != null) return;
|
if (mLeash != null || mViewHost != null) return;
|
||||||
|
|
||||||
mHostLeash = hostLeash;
|
mHostLeash = rootLeash;
|
||||||
mOutlineColor = color;
|
mRootBounds.set(rootBounds);
|
||||||
mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
|
mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
|
||||||
final OutlineRoot rootView = (OutlineRoot) LayoutInflater.from(mContext)
|
|
||||||
|
final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(mContext)
|
||||||
.inflate(R.layout.split_outline, null);
|
.inflate(R.layout.split_outline, null);
|
||||||
|
mOutlineView = rootLayout.findViewById(R.id.split_outline);
|
||||||
|
|
||||||
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
|
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
|
||||||
0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY,
|
0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY,
|
||||||
FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
|
FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
|
||||||
|
lp.width = mRootBounds.width();
|
||||||
|
lp.height = mRootBounds.height();
|
||||||
lp.token = new Binder();
|
lp.token = new Binder();
|
||||||
lp.setTitle(WINDOW_NAME);
|
lp.setTitle(WINDOW_NAME);
|
||||||
lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
|
lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
|
||||||
// TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports
|
// TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports
|
||||||
// TRUSTED_OVERLAY for windowless window without input channel.
|
// TRUSTED_OVERLAY for windowless window without input channel.
|
||||||
mViewHost.setView(rootView, lp);
|
mViewHost.setView(rootLayout, lp);
|
||||||
mLeash = getSurfaceControl(mViewHost.getWindowToken());
|
mLeash = getSurfaceControl(mViewHost.getWindowToken());
|
||||||
t.setLayer(mLeash, Integer.MAX_VALUE);
|
|
||||||
|
drawOutline();
|
||||||
}
|
}
|
||||||
|
|
||||||
void release() {
|
void release() {
|
||||||
if (mViewHost != null) {
|
if (mViewHost != null) {
|
||||||
mViewHost.release();
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void computeOutlineBounds(Context context, Rect rootBounds, Rect outBounds) {
|
void setRootBounds(Rect rootBounds) {
|
||||||
computeDisplayStableBounds(context, outBounds);
|
if (mViewHost == null || mViewHost.getView() == null) {
|
||||||
outBounds.intersect(rootBounds);
|
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.
|
// Offset the coordinate from screen based to surface based.
|
||||||
outBounds.offset(-rootBounds.left, -rootBounds.top);
|
outBounds.offset(-rootBounds.left, -rootBounds.top);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void computeDisplayStableBounds(Context context, Rect outBounds) {
|
void drawOutline() {
|
||||||
final WindowMetrics windowMetrics =
|
if (mOutlineView == null) {
|
||||||
context.getSystemService(WindowManager.class).getMaximumWindowMetrics();
|
return;
|
||||||
outBounds.set(windowMetrics.getBounds());
|
}
|
||||||
outBounds.inset(windowMetrics.getWindowInsets().getInsets(
|
|
||||||
WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout()));
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +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 android.content.Context;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.android.wm.shell.R;
|
|
||||||
|
|
||||||
/** Root layout for holding split outline. */
|
|
||||||
public class OutlineRoot extends FrameLayout {
|
|
||||||
public OutlineRoot(@NonNull Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public OutlineRoot(@NonNull Context context,
|
|
||||||
@Nullable AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public OutlineRoot(@NonNull Context context, @Nullable AttributeSet attrs,
|
|
||||||
int defStyleAttr) {
|
|
||||||
super(context, attrs, defStyleAttr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public OutlineRoot(@NonNull Context context, @Nullable AttributeSet attrs,
|
|
||||||
int defStyleAttr,
|
|
||||||
int defStyleRes) {
|
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private OutlineView mOutlineView;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onFinishInflate() {
|
|
||||||
super.onFinishInflate();
|
|
||||||
mOutlineView = findViewById(R.id.split_outline);
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateOutlineBounds(Rect bounds, int color) {
|
|
||||||
mOutlineView.updateOutlineBounds(bounds, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,13 +16,17 @@
|
|||||||
|
|
||||||
package com.android.wm.shell.splitscreen;
|
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.content.Context;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.graphics.Region;
|
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.view.RoundedCorner;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -33,44 +37,46 @@ import com.android.internal.R;
|
|||||||
/** View for drawing split outline. */
|
/** View for drawing split outline. */
|
||||||
public class OutlineView extends View {
|
public class OutlineView extends View {
|
||||||
private final Paint mPaint = new Paint();
|
private final Paint mPaint = new Paint();
|
||||||
private final Rect mBounds = new Rect();
|
private final Path mPath = new Path();
|
||||||
|
private final float[] mRadii = new float[8];
|
||||||
|
|
||||||
public OutlineView(@NonNull Context context) {
|
public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public OutlineView(@NonNull Context context,
|
|
||||||
@Nullable AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
}
|
mPaint.setStyle(Paint.Style.STROKE);
|
||||||
|
mPaint.setStrokeWidth(
|
||||||
public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
getResources().getDimension(R.dimen.accessibility_focus_highlight_stroke_width));
|
||||||
super(context, attrs, defStyleAttr);
|
mPaint.setColor(getResources().getColor(R.color.system_accent1_100, null));
|
||||||
}
|
|
||||||
|
|
||||||
public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
|
|
||||||
int defStyleRes) {
|
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onFinishInflate() {
|
protected void onAttachedToWindow() {
|
||||||
super.onFinishInflate();
|
// TODO(b/200850654): match the screen corners with the actual display decor.
|
||||||
mPaint.setStyle(Paint.Style.STROKE);
|
mRadii[0] = mRadii[1] = getCornerRadius(POSITION_TOP_LEFT);
|
||||||
mPaint.setStrokeWidth(getResources()
|
mRadii[2] = mRadii[3] = getCornerRadius(POSITION_TOP_RIGHT);
|
||||||
.getDimension(R.dimen.accessibility_focus_highlight_stroke_width));
|
mRadii[4] = mRadii[5] = getCornerRadius(POSITION_BOTTOM_RIGHT);
|
||||||
|
mRadii[6] = mRadii[7] = getCornerRadius(POSITION_BOTTOM_LEFT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateOutlineBounds(Rect bounds, int color) {
|
private int getCornerRadius(@RoundedCorner.Position int position) {
|
||||||
if (mBounds.equals(bounds) && mPaint.getColor() == color) return;
|
final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(position);
|
||||||
mBounds.set(bounds);
|
return roundedCorner == null ? 0 : roundedCorner.getRadius();
|
||||||
mPaint.setColor(color);
|
}
|
||||||
|
|
||||||
|
@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
|
@Override
|
||||||
protected void onDraw(Canvas canvas) {
|
protected void onDraw(Canvas canvas) {
|
||||||
if (mBounds.isEmpty()) return;
|
canvas.drawPath(mPath, mPaint);
|
||||||
final Path path = new Region(mBounds).getBoundaryPath();
|
}
|
||||||
canvas.drawPath(path, mPaint);
|
|
||||||
|
@Override
|
||||||
|
public boolean hasOverlappingRendering() {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,15 +17,19 @@
|
|||||||
package com.android.wm.shell.splitscreen;
|
package com.android.wm.shell.splitscreen;
|
||||||
|
|
||||||
import android.annotation.CallSuper;
|
import android.annotation.CallSuper;
|
||||||
|
import android.annotation.Nullable;
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.view.InsetsSourceControl;
|
||||||
|
import android.view.InsetsState;
|
||||||
|
import android.view.SurfaceControl;
|
||||||
import android.view.SurfaceSession;
|
import android.view.SurfaceSession;
|
||||||
import android.window.WindowContainerToken;
|
import android.window.WindowContainerToken;
|
||||||
import android.window.WindowContainerTransaction;
|
import android.window.WindowContainerTransaction;
|
||||||
|
|
||||||
import com.android.wm.shell.ShellTaskOrganizer;
|
import com.android.wm.shell.ShellTaskOrganizer;
|
||||||
|
import com.android.wm.shell.common.DisplayInsetsController;
|
||||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
import com.android.wm.shell.common.SyncTransactionQueue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,7 +38,8 @@ import com.android.wm.shell.common.SyncTransactionQueue;
|
|||||||
*
|
*
|
||||||
* @see StageCoordinator
|
* @see StageCoordinator
|
||||||
*/
|
*/
|
||||||
class SideStage extends StageTaskListener {
|
class SideStage extends StageTaskListener implements
|
||||||
|
DisplayInsetsController.OnInsetsChangedListener {
|
||||||
private static final String TAG = SideStage.class.getSimpleName();
|
private static final String TAG = SideStage.class.getSimpleName();
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private OutlineManager mOutlineManager;
|
private OutlineManager mOutlineManager;
|
||||||
@@ -77,33 +82,61 @@ class SideStage extends StageTaskListener {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void enableOutline(boolean enable) {
|
@Nullable
|
||||||
if (enable) {
|
public SurfaceControl getOutlineLeash() {
|
||||||
if (mOutlineManager == null && mRootTaskInfo != null) {
|
return mOutlineManager.getOutlineLeash();
|
||||||
mOutlineManager = new OutlineManager(mContext, mRootTaskInfo.configuration);
|
|
||||||
mSyncQueue.runInSync(t -> mOutlineManager.inflate(t, mRootLeash, Color.YELLOW));
|
|
||||||
updateOutlineBounds();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (mOutlineManager != null) {
|
|
||||||
mOutlineManager.release();
|
|
||||||
mOutlineManager = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateOutlineBounds() {
|
@Override
|
||||||
if (mOutlineManager == null || mRootTaskInfo == null || !mRootTaskInfo.isVisible) return;
|
@CallSuper
|
||||||
mOutlineManager.drawOutlineBounds(
|
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
|
||||||
mRootTaskInfo.configuration.windowConfiguration.getBounds());
|
super.onTaskAppeared(taskInfo, leash);
|
||||||
|
if (isRootTask(taskInfo)) {
|
||||||
|
mOutlineManager = new OutlineManager(mContext, taskInfo.configuration);
|
||||||
|
enableOutline(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@CallSuper
|
@CallSuper
|
||||||
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
|
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
|
||||||
super.onTaskInfoChanged(taskInfo);
|
super.onTaskInfoChanged(taskInfo);
|
||||||
if (mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId) {
|
if (isRootTask(taskInfo)) {
|
||||||
updateOutlineBounds();
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,11 +257,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final RemoteAnimationTarget divider = mStageCoordinator.getDividerBarLegacyTarget();
|
|
||||||
if (divider.leash != null) {
|
|
||||||
t.show(divider.leash);
|
|
||||||
}
|
|
||||||
|
|
||||||
t.apply();
|
t.apply();
|
||||||
if (finishedCallback != null) {
|
if (finishedCallback != null) {
|
||||||
try {
|
try {
|
||||||
@@ -300,7 +295,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
|
|||||||
}
|
}
|
||||||
transaction.apply();
|
transaction.apply();
|
||||||
transaction.close();
|
transaction.close();
|
||||||
return new RemoteAnimationTarget[]{mStageCoordinator.getDividerBarLegacyTarget()};
|
return new RemoteAnimationTarget[]{
|
||||||
|
mStageCoordinator.getDividerBarLegacyTarget(),
|
||||||
|
mStageCoordinator.getOutlineLegacyTarget()};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -201,6 +201,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
|
|||||||
mSurfaceSession);
|
mSurfaceSession);
|
||||||
mDisplayImeController = displayImeController;
|
mDisplayImeController = displayImeController;
|
||||||
mDisplayInsetsController = displayInsetsController;
|
mDisplayInsetsController = displayInsetsController;
|
||||||
|
mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSideStage);
|
||||||
mRootTDAOrganizer.registerListener(displayId, this);
|
mRootTDAOrganizer.registerListener(displayId, this);
|
||||||
final DeviceStateManager deviceStateManager =
|
final DeviceStateManager deviceStateManager =
|
||||||
mContext.getSystemService(DeviceStateManager.class);
|
mContext.getSystemService(DeviceStateManager.class);
|
||||||
@@ -682,6 +683,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
|
|||||||
t.setVisibility(mSideStage.mRootLeash, bothStageVisible)
|
t.setVisibility(mSideStage.mRootLeash, bothStageVisible)
|
||||||
.setVisibility(mMainStage.mRootLeash, bothStageVisible);
|
.setVisibility(mMainStage.mRootLeash, bothStageVisible);
|
||||||
applyDividerVisibility(t);
|
applyDividerVisibility(t);
|
||||||
|
applyOutlineVisibility(t);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -703,6 +705,19 @@ 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) {
|
private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
|
||||||
final boolean hasChildren = stageListener.mHasChildren;
|
final boolean hasChildren = stageListener.mHasChildren;
|
||||||
final boolean isSideStage = stageListener == mSideStageListener;
|
final boolean isSideStage = stageListener == mSideStageListener;
|
||||||
@@ -762,6 +777,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
|
|||||||
@Override
|
@Override
|
||||||
public void onLayoutChanging(SplitLayout layout) {
|
public void onLayoutChanging(SplitLayout layout) {
|
||||||
mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
|
mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
|
||||||
|
mSideStage.setOutlineVisibility(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -770,6 +786,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
|
|||||||
updateWindowBounds(layout, wct);
|
updateWindowBounds(layout, wct);
|
||||||
mSyncQueue.queue(wct);
|
mSyncQueue.queue(wct);
|
||||||
mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
|
mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
|
||||||
|
mSideStage.setOutlineVisibility(true);
|
||||||
mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
|
mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1145,6 +1162,18 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
|
|||||||
null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
|
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
|
@Override
|
||||||
public void dump(@NonNull PrintWriter pw, String prefix) {
|
public void dump(@NonNull PrintWriter pw, String prefix) {
|
||||||
final String innerPrefix = prefix + " ";
|
final String innerPrefix = prefix + " ";
|
||||||
|
|||||||
@@ -138,8 +138,11 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
|
|||||||
mRootTaskInfo = taskInfo;
|
mRootTaskInfo = taskInfo;
|
||||||
mCallbacks.onRootTaskAppeared();
|
mCallbacks.onRootTaskAppeared();
|
||||||
sendStatusChanged();
|
sendStatusChanged();
|
||||||
mSyncQueue.runInSync(t -> mDimLayer =
|
mSyncQueue.runInSync(t -> {
|
||||||
SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession));
|
t.hide(mRootLeash);
|
||||||
|
mDimLayer =
|
||||||
|
SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession);
|
||||||
|
});
|
||||||
} else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
|
} else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
|
||||||
final int taskId = taskInfo.taskId;
|
final int taskId = taskInfo.taskId;
|
||||||
mChildrenLeashes.put(taskId, leash);
|
mChildrenLeashes.put(taskId, leash);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import android.view.SurfaceControl;
|
|||||||
import android.view.SurfaceSession;
|
import android.view.SurfaceSession;
|
||||||
import android.window.WindowContainerTransaction;
|
import android.window.WindowContainerTransaction;
|
||||||
|
|
||||||
|
import androidx.test.annotation.UiThreadTest;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import androidx.test.filters.SmallTest;
|
import androidx.test.filters.SmallTest;
|
||||||
|
|
||||||
@@ -58,6 +59,7 @@ public class SideStageTests extends ShellTestCase {
|
|||||||
private SideStage mSideStage;
|
private SideStage mSideStage;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
@UiThreadTest
|
||||||
public void setup() {
|
public void setup() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
mRootTask = new TestRunningTaskInfoBuilder().build();
|
mRootTask = new TestRunningTaskInfoBuilder().build();
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ import android.window.TransitionInfo;
|
|||||||
import android.window.TransitionRequestInfo;
|
import android.window.TransitionRequestInfo;
|
||||||
import android.window.WindowContainerTransaction;
|
import android.window.WindowContainerTransaction;
|
||||||
|
|
||||||
|
import androidx.test.annotation.UiThreadTest;
|
||||||
import androidx.test.filters.SmallTest;
|
import androidx.test.filters.SmallTest;
|
||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
@@ -96,6 +97,7 @@ public class SplitTransitionTests extends ShellTestCase {
|
|||||||
private ActivityManager.RunningTaskInfo mSideChild;
|
private ActivityManager.RunningTaskInfo mSideChild;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
@UiThreadTest
|
||||||
public void setup() {
|
public void setup() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
final ShellExecutor mockExecutor = mock(ShellExecutor.class);
|
final ShellExecutor mockExecutor = mock(ShellExecutor.class);
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ public final class StageTaskListenerTests {
|
|||||||
@Mock private SyncTransactionQueue mSyncQueue;
|
@Mock private SyncTransactionQueue mSyncQueue;
|
||||||
@Captor private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor;
|
@Captor private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor;
|
||||||
private SurfaceSession mSurfaceSession = new SurfaceSession();
|
private SurfaceSession mSurfaceSession = new SurfaceSession();
|
||||||
|
private SurfaceControl mSurfaceControl;
|
||||||
private ActivityManager.RunningTaskInfo mRootTask;
|
private ActivityManager.RunningTaskInfo mRootTask;
|
||||||
private StageTaskListener mStageTaskListener;
|
private StageTaskListener mStageTaskListener;
|
||||||
|
|
||||||
@@ -76,7 +77,8 @@ public final class StageTaskListenerTests {
|
|||||||
mSurfaceSession);
|
mSurfaceSession);
|
||||||
mRootTask = new TestRunningTaskInfoBuilder().build();
|
mRootTask = new TestRunningTaskInfoBuilder().build();
|
||||||
mRootTask.parentTaskId = INVALID_TASK_ID;
|
mRootTask.parentTaskId = INVALID_TASK_ID;
|
||||||
mStageTaskListener.onTaskAppeared(mRootTask, new SurfaceControl());
|
mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession).setName("test").build();
|
||||||
|
mStageTaskListener.onTaskAppeared(mRootTask, mSurfaceControl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -103,7 +105,7 @@ public final class StageTaskListenerTests {
|
|||||||
final ActivityManager.RunningTaskInfo childTask =
|
final ActivityManager.RunningTaskInfo childTask =
|
||||||
new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
|
new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
|
||||||
|
|
||||||
mStageTaskListener.onTaskAppeared(childTask, new SurfaceControl());
|
mStageTaskListener.onTaskAppeared(childTask, mSurfaceControl);
|
||||||
|
|
||||||
assertThat(mStageTaskListener.mChildrenTaskInfo.contains(childTask.taskId)).isTrue();
|
assertThat(mStageTaskListener.mChildrenTaskInfo.contains(childTask.taskId)).isTrue();
|
||||||
verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(true));
|
verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(true));
|
||||||
|
|||||||
Reference in New Issue
Block a user