Merge "Stop assuming the existence of displays when tasks appear" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
54300c5851
@@ -95,6 +95,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
|
||||
mDragResizeCallback = dragResizeCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
|
||||
final int shadowRadiusDp = taskInfo.isFocused
|
||||
? DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP : DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP;
|
||||
@@ -119,8 +120,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
|
||||
});
|
||||
|
||||
if (mResult.mRootView == null) {
|
||||
// This means the task is hidden. Nothing is set up in this case including the
|
||||
// decoration surface.
|
||||
// This means something blocks the window decor from showing, e.g. the task is hidden.
|
||||
// Nothing is set up in this case including the decoration surface.
|
||||
return;
|
||||
}
|
||||
if (oldRootView != mResult.mRootView) {
|
||||
|
||||
@@ -38,6 +38,8 @@ import android.window.WindowContainerTransaction;
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.common.DisplayController;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Manages a container surface and a windowless window to show window decoration. Responsible to
|
||||
* update window decoration window state and layout parameters on task info changes and so that
|
||||
@@ -53,7 +55,8 @@ import com.android.wm.shell.common.DisplayController;
|
||||
*
|
||||
* @param <T> The type of the root view
|
||||
*/
|
||||
public class WindowDecoration<T extends View & TaskFocusStateConsumer> implements AutoCloseable {
|
||||
public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
|
||||
implements AutoCloseable {
|
||||
private static final int[] CAPTION_INSETS_TYPES = { InsetsState.ITYPE_CAPTION_BAR };
|
||||
|
||||
/**
|
||||
@@ -62,6 +65,20 @@ public class WindowDecoration<T extends View & TaskFocusStateConsumer> implement
|
||||
final Context mContext;
|
||||
final DisplayController mDisplayController;
|
||||
final ShellTaskOrganizer mTaskOrganizer;
|
||||
final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
|
||||
final SurfaceControlViewHostFactory mSurfaceControlViewHostFactory;
|
||||
private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener =
|
||||
new DisplayController.OnDisplaysChangedListener() {
|
||||
@Override
|
||||
public void onDisplayAdded(int displayId) {
|
||||
if (mTaskInfo.displayId != displayId) {
|
||||
return;
|
||||
}
|
||||
|
||||
mDisplayController.removeDisplayWindowListener(this);
|
||||
relayout(mTaskInfo);
|
||||
}
|
||||
};
|
||||
|
||||
RunningTaskInfo mTaskInfo;
|
||||
final SurfaceControl mTaskSurface;
|
||||
@@ -71,7 +88,7 @@ public class WindowDecoration<T extends View & TaskFocusStateConsumer> implement
|
||||
SurfaceControl mDecorationContainerSurface;
|
||||
SurfaceControl mTaskBackgroundSurface;
|
||||
|
||||
private CaptionWindowManager mCaptionWindowManager;
|
||||
private final CaptionWindowManager mCaptionWindowManager;
|
||||
private SurfaceControlViewHost mViewHost;
|
||||
|
||||
private final Rect mCaptionInsetsRect = new Rect();
|
||||
@@ -84,11 +101,25 @@ public class WindowDecoration<T extends View & TaskFocusStateConsumer> implement
|
||||
ShellTaskOrganizer taskOrganizer,
|
||||
RunningTaskInfo taskInfo,
|
||||
SurfaceControl taskSurface) {
|
||||
this(context, displayController, taskOrganizer, taskInfo, taskSurface,
|
||||
SurfaceControl.Builder::new, new SurfaceControlViewHostFactory() {});
|
||||
}
|
||||
|
||||
WindowDecoration(
|
||||
Context context,
|
||||
DisplayController displayController,
|
||||
ShellTaskOrganizer taskOrganizer,
|
||||
RunningTaskInfo taskInfo,
|
||||
SurfaceControl taskSurface,
|
||||
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
|
||||
SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
|
||||
mContext = context;
|
||||
mDisplayController = displayController;
|
||||
mTaskOrganizer = taskOrganizer;
|
||||
mTaskInfo = taskInfo;
|
||||
mTaskSurface = taskSurface;
|
||||
mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
|
||||
mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
|
||||
|
||||
mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
|
||||
mDecorWindowContext = mContext.createConfigurationContext(mTaskInfo.getConfiguration());
|
||||
@@ -99,6 +130,15 @@ public class WindowDecoration<T extends View & TaskFocusStateConsumer> implement
|
||||
new CaptionWindowManager(mTaskInfo.getConfiguration(), mTaskSurface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by {@link WindowDecoration} to trigger a new relayout because the requirements for a
|
||||
* relayout weren't satisfied are satisfied now.
|
||||
*
|
||||
* @param taskInfo The previous {@link RunningTaskInfo} passed into {@link #relayout} or the
|
||||
* constructor.
|
||||
*/
|
||||
abstract void relayout(RunningTaskInfo taskInfo);
|
||||
|
||||
void relayout(RunningTaskInfo taskInfo, int layoutResId, T rootView, float captionHeightDp,
|
||||
Rect outsetsDp, float shadowRadiusDp, SurfaceControl.Transaction t,
|
||||
WindowContainerTransaction wct, RelayoutResult<T> outResult) {
|
||||
@@ -110,7 +150,7 @@ public class WindowDecoration<T extends View & TaskFocusStateConsumer> implement
|
||||
}
|
||||
|
||||
if (!mTaskInfo.isVisible) {
|
||||
close();
|
||||
releaseViews();
|
||||
t.hide(mTaskSurface);
|
||||
return;
|
||||
}
|
||||
@@ -123,10 +163,14 @@ public class WindowDecoration<T extends View & TaskFocusStateConsumer> implement
|
||||
rootView = null; // Clear it just in case we use it accidentally
|
||||
final Configuration taskConfig = mTaskInfo.getConfiguration();
|
||||
if (oldTaskConfig.densityDpi != taskConfig.densityDpi
|
||||
|| mDisplay == null
|
||||
|| mDisplay.getDisplayId() != mTaskInfo.displayId) {
|
||||
close();
|
||||
releaseViews();
|
||||
|
||||
mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
|
||||
if (!obtainDisplayOrRegisterListener()) {
|
||||
outResult.mRootView = null;
|
||||
return;
|
||||
}
|
||||
mDecorWindowContext = mContext.createConfigurationContext(taskConfig);
|
||||
if (layoutResId != 0) {
|
||||
outResult.mRootView =
|
||||
@@ -141,7 +185,7 @@ public class WindowDecoration<T extends View & TaskFocusStateConsumer> implement
|
||||
|
||||
// DecorationContainerSurface
|
||||
if (mDecorationContainerSurface == null) {
|
||||
final SurfaceControl.Builder builder = new SurfaceControl.Builder();
|
||||
final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
|
||||
mDecorationContainerSurface = builder
|
||||
.setName("Decor container of Task=" + mTaskInfo.taskId)
|
||||
.setContainerLayer()
|
||||
@@ -168,7 +212,7 @@ public class WindowDecoration<T extends View & TaskFocusStateConsumer> implement
|
||||
|
||||
// TaskBackgroundSurface
|
||||
if (mTaskBackgroundSurface == null) {
|
||||
final SurfaceControl.Builder builder = new SurfaceControl.Builder();
|
||||
final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
|
||||
mTaskBackgroundSurface = builder
|
||||
.setName("Background of Task=" + mTaskInfo.taskId)
|
||||
.setEffectLayer()
|
||||
@@ -195,7 +239,7 @@ public class WindowDecoration<T extends View & TaskFocusStateConsumer> implement
|
||||
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
|
||||
lp.setTrustedOverlay();
|
||||
if (mViewHost == null) {
|
||||
mViewHost = new SurfaceControlViewHost(mDecorWindowContext, mDisplay,
|
||||
mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay,
|
||||
mCaptionWindowManager, true);
|
||||
mViewHost.setView(outResult.mRootView, lp);
|
||||
} else {
|
||||
@@ -225,8 +269,22 @@ public class WindowDecoration<T extends View & TaskFocusStateConsumer> implement
|
||||
.show(mTaskSurface);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
/**
|
||||
* Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or
|
||||
* registers {@link #mOnDisplaysChangedListener} if it doesn't.
|
||||
*
|
||||
* @return {@code true} if the {@link Display} instance exists; or {@code false} otherwise
|
||||
*/
|
||||
private boolean obtainDisplayOrRegisterListener() {
|
||||
mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
|
||||
if (mDisplay == null) {
|
||||
mDisplayController.addDisplayWindowListener(mOnDisplaysChangedListener);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void releaseViews() {
|
||||
if (mViewHost != null) {
|
||||
mViewHost.release();
|
||||
mViewHost = null;
|
||||
@@ -243,6 +301,12 @@ public class WindowDecoration<T extends View & TaskFocusStateConsumer> implement
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
|
||||
releaseViews();
|
||||
}
|
||||
|
||||
static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
|
||||
int mWidth;
|
||||
int mHeight;
|
||||
@@ -267,4 +331,11 @@ public class WindowDecoration<T extends View & TaskFocusStateConsumer> implement
|
||||
super.setConfiguration(configuration);
|
||||
}
|
||||
}
|
||||
|
||||
interface SurfaceControlViewHostFactory {
|
||||
default SurfaceControlViewHost create(
|
||||
Context c, Display d, WindowlessWindowManager wmm, boolean useSfChoreographer) {
|
||||
return new SurfaceControlViewHost(c, d, wmm, useSfChoreographer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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;
|
||||
|
||||
import static org.mockito.Mockito.RETURNS_SELF;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import android.view.SurfaceControl;
|
||||
|
||||
/**
|
||||
* Helper class to provide mocks for {@link SurfaceControl.Builder} and
|
||||
* {@link SurfaceControl.Transaction} with method chaining support.
|
||||
*/
|
||||
public class MockSurfaceControlHelper {
|
||||
private MockSurfaceControlHelper() {}
|
||||
|
||||
/**
|
||||
* Creates a mock {@link SurfaceControl.Builder} that supports method chaining and return the
|
||||
* given {@link SurfaceControl} when calling {@link SurfaceControl.Builder#build()}.
|
||||
*
|
||||
* @param mockSurfaceControl the first {@link SurfaceControl} to return
|
||||
* @param mockSurfaceControls following {@link SurfaceControl} to return
|
||||
* @return the mock of {@link SurfaceControl.Builder}
|
||||
*/
|
||||
public static SurfaceControl.Builder createMockSurfaceControlBuilder(
|
||||
SurfaceControl mockSurfaceControl, SurfaceControl... mockSurfaceControls) {
|
||||
final SurfaceControl.Builder mockBuilder = mock(SurfaceControl.Builder.class, RETURNS_SELF);
|
||||
doReturn(mockSurfaceControl, (Object[]) mockSurfaceControls)
|
||||
.when(mockBuilder)
|
||||
.build();
|
||||
return mockBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mock {@link SurfaceControl.Transaction} that supports method chaining.
|
||||
* @return the mock of {@link SurfaceControl.Transaction}
|
||||
*/
|
||||
public static SurfaceControl.Transaction createMockSurfaceControlTransaction() {
|
||||
return mock(SurfaceControl.Transaction.class, RETURNS_SELF);
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,10 @@ import static org.mockito.Mockito.mock;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.WindowConfiguration;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.os.IBinder;
|
||||
import android.view.Display;
|
||||
import android.window.IWindowContainerToken;
|
||||
import android.window.WindowContainerToken;
|
||||
|
||||
@@ -38,6 +40,10 @@ public final class TestRunningTaskInfoBuilder {
|
||||
private int mParentTaskId = INVALID_TASK_ID;
|
||||
private @WindowConfiguration.ActivityType int mActivityType = ACTIVITY_TYPE_STANDARD;
|
||||
private @WindowConfiguration.WindowingMode int mWindowingMode = WINDOWING_MODE_UNDEFINED;
|
||||
private int mDisplayId = Display.DEFAULT_DISPLAY;
|
||||
private ActivityManager.TaskDescription.Builder mTaskDescriptionBuilder = null;
|
||||
private final Point mPositionInParent = new Point();
|
||||
private boolean mIsVisible = false;
|
||||
|
||||
public static WindowContainerToken createMockWCToken() {
|
||||
final IWindowContainerToken itoken = mock(IWindowContainerToken.class);
|
||||
@@ -68,17 +74,42 @@ public final class TestRunningTaskInfoBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestRunningTaskInfoBuilder setDisplayId(int displayId) {
|
||||
mDisplayId = displayId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestRunningTaskInfoBuilder setTaskDescriptionBuilder(
|
||||
ActivityManager.TaskDescription.Builder builder) {
|
||||
mTaskDescriptionBuilder = builder;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestRunningTaskInfoBuilder setPositionInParent(int x, int y) {
|
||||
mPositionInParent.set(x, y);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestRunningTaskInfoBuilder setVisible(boolean isVisible) {
|
||||
mIsVisible = isVisible;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ActivityManager.RunningTaskInfo build() {
|
||||
final ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
|
||||
info.parentTaskId = INVALID_TASK_ID;
|
||||
info.taskId = sNextTaskId++;
|
||||
info.parentTaskId = mParentTaskId;
|
||||
info.displayId = mDisplayId;
|
||||
info.configuration.windowConfiguration.setBounds(mBounds);
|
||||
info.configuration.windowConfiguration.setActivityType(mActivityType);
|
||||
info.configuration.windowConfiguration.setWindowingMode(mWindowingMode);
|
||||
info.token = mToken;
|
||||
info.isResizeable = true;
|
||||
info.supportsMultiWindow = true;
|
||||
info.taskDescription =
|
||||
mTaskDescriptionBuilder != null ? mTaskDescriptionBuilder.build() : null;
|
||||
info.positionInParent = mPositionInParent;
|
||||
info.isVisible = mIsVisible;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import static android.view.Surface.ROTATION_0;
|
||||
import static android.view.Surface.ROTATION_270;
|
||||
import static android.view.Surface.ROTATION_90;
|
||||
|
||||
import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlTransaction;
|
||||
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
|
||||
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
|
||||
|
||||
@@ -37,6 +38,7 @@ import android.view.SurfaceControl;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.wm.shell.MockSurfaceControlHelper;
|
||||
import com.android.wm.shell.ShellTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
@@ -103,7 +105,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
|
||||
final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController
|
||||
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
|
||||
TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
|
||||
oldAnimator.setSurfaceControlTransactionFactory(PipDummySurfaceControlTx::new);
|
||||
oldAnimator.setSurfaceControlTransactionFactory(
|
||||
MockSurfaceControlHelper::createMockSurfaceControlTransaction);
|
||||
oldAnimator.start();
|
||||
|
||||
final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController
|
||||
@@ -133,7 +136,7 @@ public class PipAnimationControllerTest extends ShellTestCase {
|
||||
|
||||
@Test
|
||||
public void pipTransitionAnimator_rotatedEndValue() {
|
||||
final PipDummySurfaceControlTx tx = new PipDummySurfaceControlTx();
|
||||
final SurfaceControl.Transaction tx = createMockSurfaceControlTransaction();
|
||||
final Rect startBounds = new Rect(200, 700, 400, 800);
|
||||
final Rect endBounds = new Rect(0, 0, 500, 1000);
|
||||
// Fullscreen to PiP.
|
||||
@@ -183,7 +186,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
|
||||
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
|
||||
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
|
||||
TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
|
||||
animator.setSurfaceControlTransactionFactory(PipDummySurfaceControlTx::new);
|
||||
animator.setSurfaceControlTransactionFactory(
|
||||
MockSurfaceControlHelper::createMockSurfaceControlTransaction);
|
||||
|
||||
animator.setPipAnimationCallback(mPipAnimationCallback);
|
||||
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.pip;
|
||||
|
||||
import android.graphics.Matrix;
|
||||
import android.view.SurfaceControl;
|
||||
|
||||
/**
|
||||
* A dummy {@link SurfaceControl.Transaction} class for testing purpose and supports
|
||||
* method chaining.
|
||||
*/
|
||||
public class PipDummySurfaceControlTx extends SurfaceControl.Transaction {
|
||||
@Override
|
||||
public SurfaceControl.Transaction setAlpha(SurfaceControl leash, float alpha) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SurfaceControl.Transaction setPosition(SurfaceControl leash, float x, float y) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SurfaceControl.Transaction setWindowCrop(SurfaceControl leash, int w, int h) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SurfaceControl.Transaction setCornerRadius(SurfaceControl leash, float radius) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SurfaceControl.Transaction setShadowRadius(SurfaceControl leash, float radius) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SurfaceControl.Transaction setMatrix(SurfaceControl leash, Matrix matrix,
|
||||
float[] float9) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SurfaceControl.Transaction setFrameTimelineVsync(long frameTimelineVsyncId) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import android.util.Size;
|
||||
import android.view.DisplayInfo;
|
||||
import android.window.WindowContainerToken;
|
||||
|
||||
import com.android.wm.shell.MockSurfaceControlHelper;
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.ShellTestCase;
|
||||
import com.android.wm.shell.TestShellExecutor;
|
||||
@@ -246,7 +247,8 @@ public class PipTaskOrganizerTest extends ShellTestCase {
|
||||
mPipBoundsState.setDisplayLayout(new DisplayLayout(info,
|
||||
mContext.getResources(), true, true));
|
||||
mSpiedPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA);
|
||||
mSpiedPipTaskOrganizer.setSurfaceControlTransactionFactory(PipDummySurfaceControlTx::new);
|
||||
mSpiedPipTaskOrganizer.setSurfaceControlTransactionFactory(
|
||||
MockSurfaceControlHelper::createMockSurfaceControlTransaction);
|
||||
doNothing().when(mSpiedPipTaskOrganizer).enterPipWithAlphaAnimation(any(), anyLong());
|
||||
doNothing().when(mSpiedPipTaskOrganizer).scheduleAnimateResizePip(any(), anyInt(), any());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.windowdecor;
|
||||
|
||||
import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlBuilder;
|
||||
import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlTransaction;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.anyBoolean;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.same;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.view.Display;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.SurfaceControlViewHost;
|
||||
import android.view.View;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
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.DisplayController;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Tests for {@link WindowDecoration}.
|
||||
*
|
||||
* Build/Install/Run:
|
||||
* atest WMShellUnitTests:WindowDecorationTests
|
||||
*/
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class WindowDecorationTests extends ShellTestCase {
|
||||
private static final int CAPTION_HEIGHT_DP = 32;
|
||||
private static final int SHADOW_RADIUS_DP = 5;
|
||||
|
||||
private final Rect mOutsetsDp = new Rect();
|
||||
private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =
|
||||
new WindowDecoration.RelayoutResult<>();
|
||||
|
||||
@Mock
|
||||
private DisplayController mMockDisplayController;
|
||||
@Mock
|
||||
private ShellTaskOrganizer mMockShellTaskOrganizer;
|
||||
@Mock
|
||||
private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory;
|
||||
@Mock
|
||||
private SurfaceControlViewHost mMockSurfaceControlViewHost;
|
||||
@Mock
|
||||
private TestView mMockView;
|
||||
@Mock
|
||||
private WindowContainerTransaction mMockWindowContainerTransaction;
|
||||
|
||||
private SurfaceControl.Builder mMockSurfaceControlBuilder;
|
||||
private SurfaceControl.Transaction mMockSurfaceControlTransaction;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mMockSurfaceControlBuilder = createMockSurfaceControlBuilder(mock(SurfaceControl.class));
|
||||
mMockSurfaceControlTransaction = createMockSurfaceControlTransaction();
|
||||
|
||||
doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
|
||||
.create(any(), any(), any(), anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotCrashWhenDisplayAppearsAfterTask() {
|
||||
doReturn(mock(Display.class)).when(mMockDisplayController)
|
||||
.getDisplay(Display.DEFAULT_DISPLAY);
|
||||
|
||||
final int displayId = Display.DEFAULT_DISPLAY + 1;
|
||||
final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
|
||||
new ActivityManager.TaskDescription.Builder()
|
||||
.setBackgroundColor(Color.BLACK);
|
||||
final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
|
||||
.setDisplayId(displayId)
|
||||
.setTaskDescriptionBuilder(taskDescriptionBuilder)
|
||||
.setVisible(true)
|
||||
.build();
|
||||
|
||||
final TestWindowDecoration windowDecor =
|
||||
createWindowDecoration(taskInfo, new SurfaceControl());
|
||||
windowDecor.relayout(taskInfo);
|
||||
|
||||
// It shouldn't show the window decoration when it can't obtain the display instance.
|
||||
assertThat(mRelayoutResult.mRootView).isNull();
|
||||
|
||||
final ArgumentCaptor<DisplayController.OnDisplaysChangedListener> listenerArgumentCaptor =
|
||||
ArgumentCaptor.forClass(DisplayController.OnDisplaysChangedListener.class);
|
||||
verify(mMockDisplayController).addDisplayWindowListener(listenerArgumentCaptor.capture());
|
||||
final DisplayController.OnDisplaysChangedListener listener =
|
||||
listenerArgumentCaptor.getValue();
|
||||
|
||||
// Adding an irrelevant display shouldn't change the result.
|
||||
listener.onDisplayAdded(Display.DEFAULT_DISPLAY);
|
||||
assertThat(mRelayoutResult.mRootView).isNull();
|
||||
|
||||
final Display mockDisplay = mock(Display.class);
|
||||
doReturn(mockDisplay).when(mMockDisplayController).getDisplay(displayId);
|
||||
|
||||
listener.onDisplayAdded(displayId);
|
||||
|
||||
// The listener should be removed when the display shows up.
|
||||
verify(mMockDisplayController).removeDisplayWindowListener(same(listener));
|
||||
|
||||
assertThat(mRelayoutResult.mRootView).isSameInstanceAs(mMockView);
|
||||
verify(mMockSurfaceControlViewHostFactory)
|
||||
.create(any(), eq(mockDisplay), any(), anyBoolean());
|
||||
verify(mMockSurfaceControlViewHost).setView(same(mMockView), any());
|
||||
}
|
||||
|
||||
private TestWindowDecoration createWindowDecoration(
|
||||
ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
|
||||
return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
|
||||
taskInfo, testSurface, () -> mMockSurfaceControlBuilder,
|
||||
mMockSurfaceControlViewHostFactory);
|
||||
}
|
||||
|
||||
private static class TestView extends View implements TaskFocusStateConsumer {
|
||||
private TestView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTaskFocusState(boolean focused) {}
|
||||
}
|
||||
|
||||
private class TestWindowDecoration extends WindowDecoration<TestView> {
|
||||
TestWindowDecoration(Context context, DisplayController displayController,
|
||||
ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo,
|
||||
SurfaceControl taskSurface,
|
||||
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
|
||||
SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
|
||||
super(context, displayController, taskOrganizer, taskInfo, taskSurface,
|
||||
surfaceControlBuilderSupplier, surfaceControlViewHostFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
|
||||
relayout(null /* taskInfo */, 0 /* layoutResId */, mMockView, CAPTION_HEIGHT_DP,
|
||||
mOutsetsDp, SHADOW_RADIUS_DP, mMockSurfaceControlTransaction,
|
||||
mMockWindowContainerTransaction, mRelayoutResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user