Merge "Add border to indicate side stage split region" into sc-v2-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
a8424ea593
26
libs/WindowManager/Shell/res/layout/split_outline.xml
Normal file
26
libs/WindowManager/Shell/res/layout/split_outline.xml
Normal 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>
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +152,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
|
||||
mSyncQueue,
|
||||
mSurfaceSession);
|
||||
mSideStage = new SideStage(
|
||||
mContext,
|
||||
mTaskOrganizer,
|
||||
mDisplayId,
|
||||
mSideStageListener,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user