Merge "Add border to indicate side stage split region" into sc-v2-dev

This commit is contained in:
TreeHugger Robot
2021-06-23 06:04:30 +00:00
committed by Android (Google) Code Review
9 changed files with 328 additions and 8 deletions

View File

@@ -0,0 +1,26 @@
<?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.
-->
<com.android.wm.shell.splitscreen.OutlineRoot
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.android.wm.shell.splitscreen.OutlineView
android:id="@+id/split_outline"
android:layout_height="match_parent"
android:layout_width="match_parent" />
</com.android.wm.shell.splitscreen.OutlineRoot>

View File

@@ -311,9 +311,8 @@ public final class SplitLayout {
return context.getSystemService(WindowManager.class)
.getMaximumWindowMetrics()
.getWindowInsets()
.getInsets(WindowInsets.Type.navigationBars()
| WindowInsets.Type.statusBars()
| WindowInsets.Type.displayCutout()).toRect();
.getInsets(WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout())
.toRect();
}
private static boolean isLandscape(Rect bounds) {

View File

@@ -0,0 +1,125 @@
/*
* 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.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.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
import android.view.WindowlessWindowManager;
import com.android.wm.shell.R;
import java.util.function.Supplier;
/**
* 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 int mOutlineColor;
private final Rect mOutlineBounds = new Rect();
private final Rect mTmpBounds = new Rect();
private final Supplier<SurfaceControl> mOutlineSurfaceSupplier;
private SurfaceControlViewHost mViewHost;
/**
* Constructs {@link #OutlineManager} with indicated outline color for the provided root
* surface.
*/
OutlineManager(Context context, Configuration configuration,
Supplier<SurfaceControl> outlineSurfaceSupplier, int color) {
super(configuration, null /* rootSurface */, null /* hostInputToken */);
mContext = context.createDisplayContext(context.getDisplay());
mOutlineSurfaceSupplier = outlineSurfaceSupplier;
mOutlineColor = color;
}
@Override
protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
b.setParent(mOutlineSurfaceSupplier.get());
}
boolean updateOutlineBounds(Rect rootBounds) {
computeOutlineBounds(mContext, rootBounds, mTmpBounds);
if (mOutlineBounds.equals(mTmpBounds)) {
return false;
}
mOutlineBounds.set(mTmpBounds);
if (mViewHost == null) {
mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
}
if (mViewHost.getView() == null) {
final OutlineRoot rootView = (OutlineRoot) LayoutInflater.from(mContext)
.inflate(R.layout.split_outline, null);
rootView.updateOutlineBounds(mOutlineBounds, mOutlineColor);
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
rootBounds.width(), rootBounds.height(),
TYPE_APPLICATION_OVERLAY,
FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE,
PixelFormat.TRANSLUCENT);
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(rootView, lp);
} else {
((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;
}
private static void computeOutlineBounds(Context context, Rect rootBounds, Rect outBounds) {
computeDisplayStableBounds(context, outBounds);
outBounds.intersect(rootBounds);
// Offset the coordinate from screen based to surface based.
outBounds.offset(-rootBounds.left, -rootBounds.top);
}
private static void computeDisplayStableBounds(Context context, Rect outBounds) {
final WindowMetrics windowMetrics =
context.getSystemService(WindowManager.class).getMaximumWindowMetrics();
outBounds.set(windowMetrics.getBounds());
outBounds.inset(windowMetrics.getWindowInsets().getInsets(
WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout()));
}
}

View File

@@ -0,0 +1,62 @@
/*
* 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);
}
}

View File

@@ -0,0 +1,76 @@
/*
* 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.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.Region;
import android.util.AttributeSet;
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 Rect mBounds = new Rect();
public OutlineView(@NonNull Context context) {
super(context);
}
public OutlineView(@NonNull Context context,
@Nullable AttributeSet attrs) {
super(context, attrs);
}
public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(getResources()
.getDimension(R.dimen.accessibility_focus_highlight_stroke_width));
}
void updateOutlineBounds(Rect bounds, int color) {
if (mBounds.equals(bounds) && mPaint.getColor() == color) return;
mBounds.set(bounds);
mPaint.setColor(color);
}
@Override
protected void onDraw(Canvas canvas) {
if (mBounds.isEmpty()) return;
final Path path = new Region(mBounds).getBoundaryPath();
canvas.drawPath(path, mPaint);
}
}

View File

@@ -16,8 +16,12 @@
package com.android.wm.shell.splitscreen;
import android.annotation.CallSuper;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -28,15 +32,19 @@ import com.android.wm.shell.common.SyncTransactionQueue;
/**
* Side stage for split-screen mode. Only tasks that are explicitly pinned to this stage show up
* here. All other task are launch in the {@link MainStage}.
*
* @see StageCoordinator
*/
class SideStage extends StageTaskListener {
private static final String TAG = SideStage.class.getSimpleName();
private final Context mContext;
private OutlineManager mOutlineManager;
SideStage(ShellTaskOrganizer taskOrganizer, int displayId,
SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
SurfaceSession surfaceSession) {
super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession);
mContext = context;
}
void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds,
@@ -69,4 +77,26 @@ class SideStage extends StageTaskListener {
wct.reparent(task.token, newParent, false /* onTop */);
return true;
}
@Override
@CallSuper
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
super.onTaskAppeared(taskInfo, leash);
if (mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId) {
mOutlineManager = new OutlineManager(mContext, mRootTaskInfo.configuration,
() -> mRootLeash,
Color.YELLOW);
}
}
@Override
@CallSuper
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
super.onTaskInfoChanged(taskInfo);
if (mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId
&& mRootTaskInfo.isRunning) {
mOutlineManager.updateOutlineBounds(
mRootTaskInfo.configuration.windowConfiguration.getBounds());
}
}
}

View File

@@ -152,6 +152,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSyncQueue,
mSurfaceSession);
mSideStage = new SideStage(
mContext,
mTaskOrganizer,
mDisplayId,
mSideStageListener,

View File

@@ -33,6 +33,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
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;
@@ -46,7 +47,7 @@ import org.mockito.Spy;
/** Tests for {@link SideStage} */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class SideStageTests {
public class SideStageTests extends ShellTestCase {
@Mock private ShellTaskOrganizer mTaskOrganizer;
@Mock private StageTaskListener.StageListenerCallbacks mCallbacks;
@Mock private SyncTransactionQueue mSyncQueue;
@@ -60,8 +61,8 @@ public class SideStageTests {
public void setup() {
MockitoAnnotations.initMocks(this);
mRootTask = new TestRunningTaskInfoBuilder().build();
mSideStage = new SideStage(mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, mSyncQueue,
mSurfaceSession);
mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
mSyncQueue, mSurfaceSession);
mSideStage.onTaskAppeared(mRootTask, mRootLeash);
}

View File

@@ -102,7 +102,7 @@ public class SplitTransitionTests extends ShellTestCase {
mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mock(
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession);
mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
mSideStage = new SideStage(mTaskOrganizer, DEFAULT_DISPLAY, mock(
mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession);
mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,