blob: 1715a295ded3f6aebadeb58d347d547ac27e5024 [file] [log] [blame]
/*
* Copyright (C) 2016 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.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.view.InsetsState.ITYPE_LOCAL_NAVIGATION_BAR_1;
import static android.view.InsetsState.ITYPE_LOCAL_NAVIGATION_BAR_2;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.DisplayArea.Type.ANY;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.clearInvocations;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.InsetsSource;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.Comparator;
/**
* Test class for {@link WindowContainer}.
*
* Build/Install/Run:
* atest WmTests:WindowContainerTests
*/
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
public class WindowContainerTests extends WindowTestsBase {
@Test
public void testCreation() {
final TestWindowContainer w = new TestWindowContainerBuilder(mWm).setLayer(0).build();
assertNull("window must have no parent", w.getParentWindow());
assertEquals("window must have no children", 0, w.getChildrenCount());
}
@Test
public void testAdd() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).build();
final TestWindowContainer layer1 = root.addChildWindow(builder.setLayer(1));
final TestWindowContainer secondLayer1 = root.addChildWindow(builder.setLayer(1));
final TestWindowContainer layer2 = root.addChildWindow(builder.setLayer(2));
final TestWindowContainer layerNeg1 = root.addChildWindow(builder.setLayer(-1));
final TestWindowContainer layerNeg2 = root.addChildWindow(builder.setLayer(-2));
final TestWindowContainer secondLayerNeg1 = root.addChildWindow(builder.setLayer(-1));
final TestWindowContainer layer0 = root.addChildWindow(builder.setLayer(0));
assertEquals(7, root.getChildrenCount());
assertEquals(root, layer1.getParentWindow());
assertEquals(root, secondLayer1.getParentWindow());
assertEquals(root, layer2.getParentWindow());
assertEquals(root, layerNeg1.getParentWindow());
assertEquals(root, layerNeg2.getParentWindow());
assertEquals(root, secondLayerNeg1.getParentWindow());
assertEquals(root, layer0.getParentWindow());
assertEquals(layerNeg2, root.getChildAt(0));
assertEquals(secondLayerNeg1, root.getChildAt(1));
assertEquals(layerNeg1, root.getChildAt(2));
assertEquals(layer0, root.getChildAt(3));
assertEquals(layer1, root.getChildAt(4));
assertEquals(secondLayer1, root.getChildAt(5));
assertEquals(layer2, root.getChildAt(6));
assertTrue(layer1.mOnParentChangedCalled);
assertTrue(secondLayer1.mOnParentChangedCalled);
assertTrue(layer2.mOnParentChangedCalled);
assertTrue(layerNeg1.mOnParentChangedCalled);
assertTrue(layerNeg2.mOnParentChangedCalled);
assertTrue(secondLayerNeg1.mOnParentChangedCalled);
assertTrue(layer0.mOnParentChangedCalled);
}
@Test
public void testAddChildSetsSurfacePosition() {
reset(mTransaction);
try (MockSurfaceBuildingContainer top = new MockSurfaceBuildingContainer(mWm)) {
WindowContainer child = new WindowContainer(mWm);
child.setBounds(1, 1, 10, 10);
verify(mTransaction, never()).setPosition(any(), anyFloat(), anyFloat());
top.addChild(child, 0);
verify(mTransaction, times(1)).setPosition(any(), eq(1.f), eq(1.f));
}
}
@Test
public void testAdd_AlreadyHasParent() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).build();
final TestWindowContainer child1 = root.addChildWindow();
final TestWindowContainer child2 = root.addChildWindow();
boolean gotException = false;
try {
child1.addChildWindow(child2);
} catch (IllegalArgumentException e) {
gotException = true;
}
assertTrue(gotException);
gotException = false;
try {
root.addChildWindow(child2);
} catch (IllegalArgumentException e) {
gotException = true;
}
assertTrue(gotException);
}
@Test
public void testHasChild() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).build();
final TestWindowContainer child1 = root.addChildWindow();
final TestWindowContainer child2 = root.addChildWindow();
final TestWindowContainer child11 = child1.addChildWindow();
final TestWindowContainer child12 = child1.addChildWindow();
final TestWindowContainer child21 = child2.addChildWindow();
assertEquals(2, root.getChildrenCount());
assertEquals(2, child1.getChildrenCount());
assertEquals(1, child2.getChildrenCount());
assertTrue(root.hasChild(child1));
assertTrue(root.hasChild(child2));
assertTrue(root.hasChild(child11));
assertTrue(root.hasChild(child12));
assertTrue(root.hasChild(child21));
assertTrue(child1.hasChild(child11));
assertTrue(child1.hasChild(child12));
assertFalse(child1.hasChild(child21));
assertTrue(child2.hasChild(child21));
assertFalse(child2.hasChild(child11));
assertFalse(child2.hasChild(child12));
}
@Test
public void testRemoveImmediately() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).build();
final TestWindowContainer child1 = root.addChildWindow();
final TestWindowContainer child2 = root.addChildWindow();
final TestWindowContainer child11 = child1.addChildWindow();
final TestWindowContainer child12 = child1.addChildWindow();
final TestWindowContainer child21 = child2.addChildWindow();
assertNotNull(child12.getParentWindow());
child12.removeImmediately();
assertNull(child12.getParentWindow());
assertEquals(1, child1.getChildrenCount());
assertFalse(child1.hasChild(child12));
assertFalse(root.hasChild(child12));
assertTrue(root.hasChild(child2));
assertNotNull(child2.getParentWindow());
child2.removeImmediately();
assertNull(child2.getParentWindow());
assertNull(child21.getParentWindow());
assertEquals(0, child2.getChildrenCount());
assertEquals(1, root.getChildrenCount());
assertFalse(root.hasChild(child2));
assertFalse(root.hasChild(child21));
assertTrue(root.hasChild(child1));
assertTrue(root.hasChild(child11));
root.removeImmediately();
assertEquals(0, root.getChildrenCount());
}
@Test
public void testRemoveImmediatelyClearsLastSurfacePosition() {
reset(mTransaction);
try (MockSurfaceBuildingContainer top = new MockSurfaceBuildingContainer(mWm)) {
final WindowContainer<WindowContainer> child1 = new WindowContainer(mWm);
child1.setBounds(1, 1, 10, 10);
top.addChild(child1, 0);
assertEquals(1, child1.getLastSurfacePosition().x);
assertEquals(1, child1.getLastSurfacePosition().y);
WindowContainer child11 = new WindowContainer(mWm);
child1.addChild(child11, 0);
child1.setBounds(2, 2, 20, 20);
assertEquals(2, child1.getLastSurfacePosition().x);
assertEquals(2, child1.getLastSurfacePosition().y);
child1.removeImmediately();
assertEquals(0, child1.getLastSurfacePosition().x);
assertEquals(0, child1.getLastSurfacePosition().y);
assertEquals(0, child11.getLastSurfacePosition().x);
assertEquals(0, child11.getLastSurfacePosition().y);
}
}
@Test
public void testRemoveImmediatelyClearsLeash() {
final AnimationAdapter animAdapter = mock(AnimationAdapter.class);
final WindowToken token = createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDisplayContent);
final SurfaceControl.Transaction t = token.getPendingTransaction();
token.startAnimation(t, animAdapter, false /* hidden */,
SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION);
final ArgumentCaptor<SurfaceControl> leashCaptor =
ArgumentCaptor.forClass(SurfaceControl.class);
verify(animAdapter).startAnimation(leashCaptor.capture(), eq(t), anyInt(), any());
assertTrue(token.mSurfaceAnimator.hasLeash());
token.removeImmediately();
assertFalse(token.mSurfaceAnimator.hasLeash());
verify(t).remove(eq(leashCaptor.getValue()));
}
@Test
public void testAddChildByIndex() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).build();
final TestWindowContainer child = root.addChildWindow();
final TestWindowContainer child2 = builder.setLayer(1).build();
final TestWindowContainer child3 = builder.setLayer(2).build();
final TestWindowContainer child4 = builder.setLayer(3).build();
// Test adding at top.
root.addChild(child2, POSITION_TOP);
assertEquals(child2, root.getChildAt(root.getChildrenCount() - 1));
// Test adding at bottom.
root.addChild(child3, POSITION_BOTTOM);
assertEquals(child3, root.getChildAt(0));
// Test adding in the middle.
root.addChild(child4, 1);
assertEquals(child3, root.getChildAt(0));
assertEquals(child4, root.getChildAt(1));
assertEquals(child, root.getChildAt(2));
assertEquals(child2, root.getChildAt(3));
}
@Test
public void testPositionChildAt() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).build();
final TestWindowContainer child1 = root.addChildWindow();
final TestWindowContainer child2 = root.addChildWindow();
final TestWindowContainer child3 = root.addChildWindow();
// Test position at top.
root.positionChildAt(POSITION_TOP, child1, false /* includingParents */);
assertEquals(child1, root.getChildAt(root.getChildrenCount() - 1));
// Test position at bottom.
root.positionChildAt(POSITION_BOTTOM, child1, false /* includingParents */);
assertEquals(child1, root.getChildAt(0));
// Test position in the middle.
root.positionChildAt(1, child3, false /* includingParents */);
assertEquals(child1, root.getChildAt(0));
assertEquals(child3, root.getChildAt(1));
assertEquals(child2, root.getChildAt(2));
}
@Test
public void testPositionChildAtIncludeParents() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).build();
final TestWindowContainer child1 = root.addChildWindow();
final TestWindowContainer child2 = root.addChildWindow();
final TestWindowContainer child11 = child1.addChildWindow();
final TestWindowContainer child12 = child1.addChildWindow();
final TestWindowContainer child13 = child1.addChildWindow();
final TestWindowContainer child21 = child2.addChildWindow();
final TestWindowContainer child22 = child2.addChildWindow();
final TestWindowContainer child23 = child2.addChildWindow();
// Test moving to top.
child1.positionChildAt(POSITION_TOP, child11, true /* includingParents */);
assertEquals(child12, child1.getChildAt(0));
assertEquals(child13, child1.getChildAt(1));
assertEquals(child11, child1.getChildAt(2));
assertEquals(child2, root.getChildAt(0));
assertEquals(child1, root.getChildAt(1));
// Test moving to bottom.
child1.positionChildAt(POSITION_BOTTOM, child11, true /* includingParents */);
assertEquals(child11, child1.getChildAt(0));
assertEquals(child12, child1.getChildAt(1));
assertEquals(child13, child1.getChildAt(2));
assertEquals(child1, root.getChildAt(0));
assertEquals(child2, root.getChildAt(1));
// Test moving to middle, includeParents shouldn't do anything.
child2.positionChildAt(1, child21, true /* includingParents */);
assertEquals(child11, child1.getChildAt(0));
assertEquals(child12, child1.getChildAt(1));
assertEquals(child13, child1.getChildAt(2));
assertEquals(child22, child2.getChildAt(0));
assertEquals(child21, child2.getChildAt(1));
assertEquals(child23, child2.getChildAt(2));
assertEquals(child1, root.getChildAt(0));
assertEquals(child2, root.getChildAt(1));
}
@Test
public void testIsAnimating_TransitionFlag() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).build();
final TestWindowContainer child1 = root.addChildWindow(
builder.setWaitForTransitionStart(true));
assertFalse(root.isAnimating(TRANSITION));
assertTrue(child1.isAnimating(TRANSITION));
}
@Test
public void testIsAnimating_ParentsFlag() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).build();
final TestWindowContainer child1 = root.addChildWindow(builder);
final TestWindowContainer child2 = root.addChildWindow(builder.setIsAnimating(true));
final TestWindowContainer child21 = child2.addChildWindow(builder.setIsAnimating(false));
assertFalse(root.isAnimating());
assertFalse(child1.isAnimating());
assertFalse(child1.isAnimating(PARENTS));
assertTrue(child2.isAnimating());
assertTrue(child2.isAnimating(PARENTS));
assertFalse(child21.isAnimating());
assertTrue(child21.isAnimating(PARENTS));
}
@Test
public void testIsAnimating_ChildrenFlag() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).build();
final TestWindowContainer child1 = root.addChildWindow(builder);
final TestWindowContainer child2 = root.addChildWindow(builder.setIsAnimating(true));
final TestWindowContainer child11 = child1.addChildWindow(builder.setIsAnimating(true));
assertFalse(root.isAnimating());
assertTrue(root.isAnimating(CHILDREN));
assertFalse(child1.isAnimating());
assertTrue(child1.isAnimating(CHILDREN));
assertTrue(child2.isAnimating());
assertTrue(child2.isAnimating(CHILDREN));
assertTrue(child11.isAnimating());
assertTrue(child11.isAnimating(CHILDREN));
}
@Test
public void testIsAnimating_combineFlags() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).build();
final TestWindowContainer child1 = root.addChildWindow(builder.setIsAnimating(true));
final TestWindowContainer child2 = root.addChildWindow();
final TestWindowContainer child11 = child1.addChildWindow();
final TestWindowContainer child12 = child1.addChildWindow(builder.setIsAnimating(true));
final TestWindowContainer child21 = child2.addChildWindow();
assertFalse(root.isAnimating(TRANSITION | PARENTS));
assertTrue(child1.isAnimating(TRANSITION | PARENTS));
assertTrue(child11.isAnimating(TRANSITION | PARENTS));
assertTrue(child12.isAnimating(TRANSITION | PARENTS));
assertFalse(child2.isAnimating(TRANSITION | PARENTS));
assertFalse(child21.isAnimating(TRANSITION | PARENTS));
assertTrue(root.isAnimating(TRANSITION | CHILDREN));
assertTrue(child1.isAnimating(TRANSITION | CHILDREN));
assertFalse(child11.isAnimating(TRANSITION | CHILDREN));
assertTrue(child12.isAnimating(TRANSITION | CHILDREN));
assertFalse(child2.isAnimating(TRANSITION | CHILDREN));
assertFalse(child21.isAnimating(TRANSITION | CHILDREN));
}
@Test
public void testIsAnimating_typesToCheck() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer window = builder.setIsAnimating(true).setLayer(0).build();
assertTrue(window.isAnimating());
assertFalse(window.isAnimating(0, ANIMATION_TYPE_SCREEN_ROTATION));
assertTrue(window.isAnimating(0, ANIMATION_TYPE_APP_TRANSITION));
assertFalse(window.isAnimating(0, ANIMATION_TYPE_ALL & ~ANIMATION_TYPE_APP_TRANSITION));
final TestWindowContainer child = window.addChildWindow();
assertFalse(child.isAnimating());
assertTrue(child.isAnimating(PARENTS));
assertTrue(child.isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION));
assertFalse(child.isAnimating(PARENTS, ANIMATION_TYPE_SCREEN_ROTATION));
final WindowState windowState = createWindow(null /* parent */, TYPE_BASE_APPLICATION,
mDisplayContent, "TestWindowState");
WindowContainer parent = windowState.getParent();
spyOn(windowState.mSurfaceAnimator);
doReturn(true).when(windowState.mSurfaceAnimator).isAnimating();
doReturn(ANIMATION_TYPE_APP_TRANSITION).when(
windowState.mSurfaceAnimator).getAnimationType();
assertTrue(parent.isAnimating(CHILDREN));
windowState.setControllableInsetProvider(mock(WindowContainerInsetsSourceProvider.class));
assertFalse(parent.isAnimating(CHILDREN));
}
@Test
public void testIsVisible() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).build();
final TestWindowContainer child1 = root.addChildWindow(builder.setIsVisible(true));
final TestWindowContainer child2 = root.addChildWindow();
final TestWindowContainer child11 = child1.addChildWindow();
final TestWindowContainer child12 = child1.addChildWindow(builder.setIsVisible(true));
final TestWindowContainer child21 = child2.addChildWindow();
assertFalse(root.isVisible());
assertTrue(child1.isVisible());
assertFalse(child11.isVisible());
assertTrue(child12.isVisible());
assertFalse(child2.isVisible());
assertFalse(child21.isVisible());
}
@Test
public void testOverrideConfigurationAncestorNotification() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer grandparent = builder.setLayer(0).build();
final TestWindowContainer parent = grandparent.addChildWindow();
final TestWindowContainer child = parent.addChildWindow();
child.onRequestedOverrideConfigurationChanged(new Configuration());
assertTrue(grandparent.mOnDescendantOverrideCalled);
}
@Test
public void testRemoveChild() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).build();
final TestWindowContainer child1 = root.addChildWindow();
final TestWindowContainer child2 = root.addChildWindow();
final TestWindowContainer child11 = child1.addChildWindow();
final TestWindowContainer child21 = child2.addChildWindow();
assertTrue(root.hasChild(child2));
assertTrue(root.hasChild(child21));
root.removeChild(child2);
assertFalse(root.hasChild(child2));
assertFalse(root.hasChild(child21));
assertNull(child2.getParentWindow());
boolean gotException = false;
assertTrue(root.hasChild(child11));
try {
// Can only detach our direct children.
root.removeChild(child11);
} catch (IllegalArgumentException e) {
gotException = true;
}
assertTrue(gotException);
}
@Test
public void testGetOrientation_childSpecified() {
testGetOrientation_childSpecifiedConfig(false, SCREEN_ORIENTATION_LANDSCAPE,
SCREEN_ORIENTATION_LANDSCAPE);
testGetOrientation_childSpecifiedConfig(false, SCREEN_ORIENTATION_UNSET,
SCREEN_ORIENTATION_UNSPECIFIED);
}
private void testGetOrientation_childSpecifiedConfig(boolean childVisible, int childOrientation,
int expectedOrientation) {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).build();
root.setFillsParent(true);
builder.setIsVisible(childVisible);
if (childOrientation != SCREEN_ORIENTATION_UNSET) {
builder.setOrientation(childOrientation);
}
final TestWindowContainer child1 = root.addChildWindow(builder);
child1.setFillsParent(true);
assertEquals(expectedOrientation, root.getOrientation());
}
@Test
public void testGetOrientation_Unset() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build();
// Unspecified well because we didn't specify anything...
assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, root.getOrientation());
}
@Test
public void testGetOrientation_InvisibleParentUnsetVisibleChildren() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build();
builder.setIsVisible(false).setLayer(-1);
final TestWindowContainer invisible = root.addChildWindow(builder);
builder.setIsVisible(true).setLayer(-2);
final TestWindowContainer invisibleChild1VisibleAndSet = invisible.addChildWindow(builder);
invisibleChild1VisibleAndSet.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
// Landscape well because the container is visible and that is what we set on it above.
assertEquals(SCREEN_ORIENTATION_LANDSCAPE, invisibleChild1VisibleAndSet.getOrientation());
// Landscape because even though the container isn't visible it has a child that is
// specifying it can influence the orientation by being visible.
assertEquals(SCREEN_ORIENTATION_LANDSCAPE, invisible.getOrientation());
// Landscape because the grandchild is visible and therefore can participate.
assertEquals(SCREEN_ORIENTATION_LANDSCAPE, root.getOrientation());
builder.setIsVisible(true).setLayer(-3);
final TestWindowContainer visibleUnset = root.addChildWindow(builder);
visibleUnset.setOrientation(SCREEN_ORIENTATION_UNSET);
assertEquals(SCREEN_ORIENTATION_UNSET, visibleUnset.getOrientation());
assertEquals(SCREEN_ORIENTATION_LANDSCAPE, root.getOrientation());
}
@Test
public void testGetOrientation_setBehind() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build();
builder.setIsVisible(true).setLayer(-1);
final TestWindowContainer visibleUnset = root.addChildWindow(builder);
visibleUnset.setOrientation(SCREEN_ORIENTATION_UNSET);
builder.setIsVisible(true).setLayer(-2);
final TestWindowContainer visibleUnsetChild1VisibleSetBehind =
visibleUnset.addChildWindow(builder);
visibleUnsetChild1VisibleSetBehind.setOrientation(SCREEN_ORIENTATION_BEHIND);
// Setting to visible behind will be used by the parents if there isn't another other
// container behind this one that has an orientation set.
assertEquals(SCREEN_ORIENTATION_BEHIND,
visibleUnsetChild1VisibleSetBehind.getOrientation());
assertEquals(SCREEN_ORIENTATION_BEHIND, visibleUnset.getOrientation());
assertEquals(SCREEN_ORIENTATION_BEHIND, root.getOrientation());
}
@Test
public void testGetOrientation_fillsParent() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build();
builder.setIsVisible(true).setLayer(-1);
final TestWindowContainer visibleUnset = root.addChildWindow(builder);
visibleUnset.setOrientation(SCREEN_ORIENTATION_BEHIND);
builder.setLayer(1).setIsVisible(true);
final TestWindowContainer visibleUnspecifiedRootChild = root.addChildWindow(builder);
visibleUnspecifiedRootChild.setFillsParent(false);
visibleUnspecifiedRootChild.setOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
// Unset because the child doesn't fill the parent. May as well be invisible...
assertEquals(SCREEN_ORIENTATION_UNSET, visibleUnspecifiedRootChild.getOrientation());
// The parent uses whatever orientation is set behind this container since it doesn't fill
// the parent.
assertEquals(SCREEN_ORIENTATION_BEHIND, root.getOrientation());
// Test case of child filling its parent, but its parent isn't filling its own parent.
builder.setLayer(2).setIsVisible(true);
final TestWindowContainer visibleUnspecifiedRootChildChildFillsParent =
visibleUnspecifiedRootChild.addChildWindow(builder);
visibleUnspecifiedRootChildChildFillsParent.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
assertEquals(SCREEN_ORIENTATION_PORTRAIT,
visibleUnspecifiedRootChildChildFillsParent.getOrientation());
assertEquals(SCREEN_ORIENTATION_UNSET, visibleUnspecifiedRootChild.getOrientation());
assertEquals(SCREEN_ORIENTATION_BEHIND, root.getOrientation());
visibleUnspecifiedRootChild.setFillsParent(true);
assertEquals(SCREEN_ORIENTATION_PORTRAIT, visibleUnspecifiedRootChild.getOrientation());
assertEquals(SCREEN_ORIENTATION_PORTRAIT, root.getOrientation());
}
@Test
public void testSetOrientation() {
final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).build());
final TestWindowContainer child = spy(root.addChildWindow());
doReturn(true).when(root).handlesOrientationChangeFromDescendant();
child.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
child.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
// The ancestor should decide whether to dispatch the configuration change.
verify(child, never()).onConfigurationChanged(any());
doReturn(false).when(root).handlesOrientationChangeFromDescendant();
child.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
// The ancestor doesn't handle the request so the descendant applies the change directly.
verify(child).onConfigurationChanged(any());
}
@Test
public void testCompareTo() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.setLayer(0).build();
final TestWindowContainer child1 = root.addChildWindow();
final TestWindowContainer child11 = child1.addChildWindow();
final TestWindowContainer child12 = child1.addChildWindow();
final TestWindowContainer child2 = root.addChildWindow();
final TestWindowContainer child21 = child2.addChildWindow();
final TestWindowContainer child22 = child2.addChildWindow();
final TestWindowContainer child222 = child22.addChildWindow();
final TestWindowContainer child223 = child22.addChildWindow();
final TestWindowContainer child2221 = child222.addChildWindow();
final TestWindowContainer child2222 = child222.addChildWindow();
final TestWindowContainer child2223 = child222.addChildWindow();
final TestWindowContainer root2 = builder.setLayer(0).build();
assertEquals(0, root.compareTo(root));
assertEquals(-1, child1.compareTo(child2));
assertEquals(1, child2.compareTo(child1));
boolean inTheSameTree = true;
try {
root.compareTo(root2);
} catch (IllegalArgumentException e) {
inTheSameTree = false;
}
assertFalse(inTheSameTree);
assertEquals(-1, child1.compareTo(child11));
assertEquals(1, child21.compareTo(root));
assertEquals(1, child21.compareTo(child12));
assertEquals(-1, child11.compareTo(child2));
assertEquals(1, child2221.compareTo(child11));
assertEquals(-1, child2222.compareTo(child223));
assertEquals(1, child2223.compareTo(child21));
}
@Test
public void testPrefixOrderIndex() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.build();
final TestWindowContainer child1 = root.addChildWindow();
final TestWindowContainer child11 = child1.addChildWindow();
final TestWindowContainer child12 = child1.addChildWindow();
final TestWindowContainer child2 = root.addChildWindow();
final TestWindowContainer child21 = child2.addChildWindow();
final TestWindowContainer child22 = child2.addChildWindow();
final TestWindowContainer child221 = child22.addChildWindow();
final TestWindowContainer child222 = child22.addChildWindow();
final TestWindowContainer child223 = child22.addChildWindow();
final TestWindowContainer child23 = child2.addChildWindow();
assertEquals(0, root.getPrefixOrderIndex());
assertEquals(1, child1.getPrefixOrderIndex());
assertEquals(2, child11.getPrefixOrderIndex());
assertEquals(3, child12.getPrefixOrderIndex());
assertEquals(4, child2.getPrefixOrderIndex());
assertEquals(5, child21.getPrefixOrderIndex());
assertEquals(6, child22.getPrefixOrderIndex());
assertEquals(7, child221.getPrefixOrderIndex());
assertEquals(8, child222.getPrefixOrderIndex());
assertEquals(9, child223.getPrefixOrderIndex());
assertEquals(10, child23.getPrefixOrderIndex());
}
@Test
public void testPrefixOrder_addEntireSubtree() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.build();
final TestWindowContainer subtree = builder.build();
final TestWindowContainer subtree2 = builder.build();
final TestWindowContainer child1 = subtree.addChildWindow();
final TestWindowContainer child11 = child1.addChildWindow();
final TestWindowContainer child2 = subtree2.addChildWindow();
final TestWindowContainer child3 = subtree2.addChildWindow();
subtree.addChild(subtree2, 1);
root.addChild(subtree, 0);
assertEquals(0, root.getPrefixOrderIndex());
assertEquals(1, subtree.getPrefixOrderIndex());
assertEquals(2, child1.getPrefixOrderIndex());
assertEquals(3, child11.getPrefixOrderIndex());
assertEquals(4, subtree2.getPrefixOrderIndex());
assertEquals(5, child2.getPrefixOrderIndex());
assertEquals(6, child3.getPrefixOrderIndex());
}
@Test
public void testPrefixOrder_remove() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.build();
final TestWindowContainer child1 = root.addChildWindow();
final TestWindowContainer child11 = child1.addChildWindow();
final TestWindowContainer child12 = child1.addChildWindow();
final TestWindowContainer child2 = root.addChildWindow();
assertEquals(0, root.getPrefixOrderIndex());
assertEquals(1, child1.getPrefixOrderIndex());
assertEquals(2, child11.getPrefixOrderIndex());
assertEquals(3, child12.getPrefixOrderIndex());
assertEquals(4, child2.getPrefixOrderIndex());
root.removeChild(child1);
assertEquals(1, child2.getPrefixOrderIndex());
}
/**
* Ensure children of a {@link WindowContainer} do not have
* {@link WindowContainer#onParentResize()} called when {@link WindowContainer#onParentResize()}
* is invoked with overridden bounds.
*/
@Test
public void testOnParentResizePropagation() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = builder.build();
final TestWindowContainer child = root.addChildWindow();
child.setBounds(new Rect(1, 1, 2, 2));
final TestWindowContainer grandChild = mock(TestWindowContainer.class);
child.addChildWindow(grandChild);
root.onResize();
// Make sure the child does not propagate resize through onParentResize when bounds are set.
verify(grandChild, never()).onParentResize();
child.removeChild(grandChild);
child.setBounds(null);
child.addChildWindow(grandChild);
root.onResize();
// Make sure the child propagates resize through onParentResize when no bounds set.
verify(grandChild, times(1)).onParentResize();
}
@Test
public void testOnDescendantOrientationRequestChangedPropagation() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = spy(builder.build());
final ActivityRecord activityRecord = mock(ActivityRecord.class);
final TestWindowContainer child = root.addChildWindow();
child.setOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED, activityRecord);
verify(root).onDescendantOrientationChanged(activityRecord);
}
@Test
public void testHandlesOrientationChangeFromDescendantProgation() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
final TestWindowContainer root = spy(builder.build());
final TestWindowContainer child = root.addChildWindow();
assertFalse(child.handlesOrientationChangeFromDescendant());
Mockito.doReturn(true).when(root).handlesOrientationChangeFromDescendant();
assertTrue(child.handlesOrientationChangeFromDescendant());
}
@Test
public void testOnDisplayChanged() {
final Task rootTask = createTask(mDisplayContent);
final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
final DisplayContent newDc = createNewDisplay();
rootTask.getDisplayArea().removeRootTask(rootTask);
newDc.getDefaultTaskDisplayArea().addChild(rootTask, POSITION_TOP);
verify(rootTask).onDisplayChanged(newDc);
verify(task).onDisplayChanged(newDc);
verify(activity).onDisplayChanged(newDc);
assertEquals(newDc, rootTask.mDisplayContent);
assertEquals(newDc, task.mDisplayContent);
assertEquals(newDc, activity.mDisplayContent);
}
@Test
public void testHandleCompleteDeferredRemoval() {
final DisplayContent displayContent = createNewDisplay();
// Do not reparent activity to default display when removing the display.
doReturn(true).when(displayContent).shouldDestroyContentOnRemove();
// An animating window with mRemoveOnExit can be removed by handleCompleteDeferredRemoval
// once it no longer animates.
final WindowState exitingWindow = createWindow(null, TYPE_APPLICATION_OVERLAY,
displayContent, "exiting window");
exitingWindow.startAnimation(exitingWindow.getPendingTransaction(),
mock(AnimationAdapter.class), false /* hidden */,
SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION);
exitingWindow.mRemoveOnExit = true;
exitingWindow.handleCompleteDeferredRemoval();
// The animation has not finished so the window is not removed.
assertTrue(exitingWindow.isAnimating());
assertTrue(exitingWindow.isAttached());
exitingWindow.cancelAnimation();
// The window is removed because the animation is gone.
exitingWindow.handleCompleteDeferredRemoval();
assertFalse(exitingWindow.isAttached());
final ActivityRecord r = new TaskBuilder(mSupervisor).setCreateActivity(true)
.setDisplay(displayContent).build().getTopMostActivity();
// Add a window and make the activity animating so the removal of activity is deferred.
createWindow(null, TYPE_BASE_APPLICATION, r, "win");
doReturn(true).when(r).isAnimating(anyInt(), anyInt());
displayContent.remove();
// Ensure that ActivityRecord#onRemovedFromDisplay is called.
r.destroyed("test");
// The removal is deferred, so the activity is still in the display.
assertEquals(r, displayContent.getTopMostActivity());
// Assume the animation is done so the deferred removal can continue.
doReturn(false).when(r).isAnimating(anyInt(), anyInt());
assertFalse(displayContent.handleCompleteDeferredRemoval());
assertFalse(displayContent.hasChild());
assertFalse(r.hasChild());
}
@Test
public void testTaskCanApplyAnimation() {
final Task rootTask = createTask(mDisplayContent);
final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
final ActivityRecord activity2 = createActivityRecord(mDisplayContent, task);
final ActivityRecord activity1 = createActivityRecord(mDisplayContent, task);
verifyWindowContainerApplyAnimation(task, activity1, activity2);
}
@Test
public void testRootTaskCanApplyAnimation() {
final Task rootTask = createTask(mDisplayContent);
final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
createTaskInRootTask(rootTask, 0 /* userId */));
final ActivityRecord activity1 = createActivityRecord(mDisplayContent,
createTaskInRootTask(rootTask, 0 /* userId */));
verifyWindowContainerApplyAnimation(rootTask, activity1, activity2);
}
@Test
public void testGetDisplayArea() {
// WindowContainer
final WindowContainer windowContainer = new WindowContainer(mWm);
assertNull(windowContainer.getDisplayArea());
// Task > WindowContainer
final Task task = createTask(mDisplayContent);
task.addChild(windowContainer, 0);
task.setParent(null);
assertNull(windowContainer.getDisplayArea());
assertNull(task.getDisplayArea());
// TaskDisplayArea > Task > WindowContainer
final TaskDisplayArea taskDisplayArea = new TaskDisplayArea(
mDisplayContent, mWm, "TaskDisplayArea", FEATURE_DEFAULT_TASK_CONTAINER);
taskDisplayArea.addChild(task, 0);
assertEquals(taskDisplayArea, windowContainer.getDisplayArea());
assertEquals(taskDisplayArea, task.getDisplayArea());
assertEquals(taskDisplayArea, taskDisplayArea.getDisplayArea());
// DisplayArea
final DisplayArea displayArea = new DisplayArea(mWm, ANY, "DisplayArea");
assertEquals(displayArea, displayArea.getDisplayArea());
}
private void verifyWindowContainerApplyAnimation(WindowContainer wc, ActivityRecord act,
ActivityRecord act2) {
// Initial remote animation for app transition.
final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
new IRemoteAnimationRunner.Stub() {
@Override
public void onAnimationStart(@WindowManager.TransitionOldType int transit,
RemoteAnimationTarget[] apps,
RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback) {
try {
finishedCallback.onAnimationFinished();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onAnimationCancelled(boolean isKeyguardOccluded) {
}
}, 0, 0, false);
adapter.setCallingPidUid(123, 456);
wc.getDisplayContent().prepareAppTransition(TRANSIT_OPEN);
wc.getDisplayContent().mAppTransition.overridePendingAppTransitionRemote(adapter);
spyOn(wc);
doReturn(true).when(wc).okToAnimate();
// Make sure animating state is as expected after applied animation.
// Animation target is promoted from act to wc. act2 is a descendant of wc, but not a source
// of the animation.
ArrayList<WindowContainer<WindowState>> sources = new ArrayList<>();
sources.add(act);
assertTrue(wc.applyAnimation(null, TRANSIT_OLD_TASK_OPEN, true, false, sources));
assertEquals(act, wc.getTopMostActivity());
assertTrue(wc.isAnimating());
assertTrue(wc.isAnimating(0, ANIMATION_TYPE_APP_TRANSITION));
assertTrue(wc.getAnimationSources().contains(act));
assertFalse(wc.getAnimationSources().contains(act2));
assertTrue(act.isAnimating(PARENTS));
assertTrue(act.isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION));
assertEquals(wc, act.getAnimatingContainer(PARENTS, ANIMATION_TYPE_APP_TRANSITION));
// Make sure animation finish callback will be received and reset animating state after
// animation finish.
wc.getDisplayContent().mAppTransition.goodToGo(TRANSIT_OLD_TASK_OPEN, act);
verify(wc).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION), any());
assertFalse(wc.isAnimating());
assertFalse(act.isAnimating(PARENTS));
}
@Test
public void testRegisterWindowContainerListener() {
final WindowContainer container = new WindowContainer(mWm);
container.mDisplayContent = mDisplayContent;
final TestWindowContainerListener listener = new TestWindowContainerListener();
Configuration config = container.getConfiguration();
Rect bounds = new Rect(0, 0, 10, 10);
config.windowConfiguration.setBounds(bounds);
config.densityDpi = 100;
container.onRequestedOverrideConfigurationChanged(config);
container.registerWindowContainerListener(listener);
assertEquals(mDisplayContent, listener.mDisplayContent);
assertEquals(bounds, listener.mConfiguration.windowConfiguration.getBounds());
assertEquals(100, listener.mConfiguration.densityDpi);
container.onDisplayChanged(mDefaultDisplay);
assertEquals(listener.mDisplayContent, mDefaultDisplay);
config = new Configuration();
bounds = new Rect(0, 0, 20, 20);
config.windowConfiguration.setBounds(bounds);
config.densityDpi = 200;
container.onRequestedOverrideConfigurationChanged(config);
assertEquals(bounds, listener.mConfiguration.windowConfiguration.getBounds());
assertEquals(200, listener.mConfiguration.densityDpi);
}
@Test
public void testFreezeInsets() {
final Task task = createTask(mDisplayContent);
final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win");
// Set visibility to false, verify the main window of the task will be set the frozen
// insets state immediately.
activity.setVisibility(false);
assertNotNull(win.getFrozenInsetsState());
// Now make it visible again, verify that the insets are immediately unfrozen.
activity.setVisibility(true);
assertNull(win.getFrozenInsetsState());
}
@Test
public void testFreezeInsetsStateWhenAppTransition() {
final Task rootTask = createTask(mDisplayContent);
final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win");
task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE);
spyOn(win);
doReturn(true).when(task).okToAnimate();
ArrayList<WindowContainer> sources = new ArrayList<>();
sources.add(activity);
// Simulate the task applying the exit transition, verify the main window of the task
// will be set the frozen insets state before the animation starts
activity.setVisibility(false);
task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */,
false /* isVoiceInteraction */, sources);
verify(win).freezeInsetsState();
// Simulate the task transition finished.
activity.commitVisibility(false, false);
task.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION,
task.mSurfaceAnimator.getAnimation());
// Now make it visible again, verify that the insets are immediately unfrozen even before
// transition starts.
activity.setVisibility(true);
verify(win).clearFrozenInsetsState();
}
@Test
public void testAssignRelativeLayer() {
final WindowContainer container = new WindowContainer(mWm);
container.mSurfaceControl = mock(SurfaceControl.class);
final SurfaceAnimator surfaceAnimator = container.mSurfaceAnimator;
final SurfaceControl relativeParent = mock(SurfaceControl.class);
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
spyOn(container);
spyOn(surfaceAnimator);
// Trigger for first relative layer call.
container.assignRelativeLayer(t, relativeParent, 1 /* layer */);
verify(surfaceAnimator).setRelativeLayer(t, relativeParent, 1 /* layer */);
// Not trigger for the same relative layer call.
clearInvocations(surfaceAnimator);
container.assignRelativeLayer(t, relativeParent, 1 /* layer */);
verify(surfaceAnimator, never()).setRelativeLayer(t, relativeParent, 1 /* layer */);
// Trigger for the same relative layer call if forceUpdate=true
container.assignRelativeLayer(t, relativeParent, 1 /* layer */, true /* forceUpdate */);
verify(surfaceAnimator).setRelativeLayer(t, relativeParent, 1 /* layer */);
}
@Test
public void testAssignAnimationLayer() {
final WindowContainer container = new WindowContainer(mWm);
container.mSurfaceControl = mock(SurfaceControl.class);
final SurfaceAnimator surfaceAnimator = container.mSurfaceAnimator;
final SurfaceFreezer surfaceFreezer = container.mSurfaceFreezer;
final SurfaceControl relativeParent = mock(SurfaceControl.class);
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
spyOn(container);
spyOn(surfaceAnimator);
spyOn(surfaceFreezer);
container.setLayer(t, 1);
container.setRelativeLayer(t, relativeParent, 2);
// Set through surfaceAnimator if surfaceFreezer doesn't have leash.
verify(surfaceAnimator).setLayer(t, 1);
verify(surfaceAnimator).setRelativeLayer(t, relativeParent, 2);
verify(surfaceFreezer, never()).setLayer(any(), anyInt());
verify(surfaceFreezer, never()).setRelativeLayer(any(), any(), anyInt());
clearInvocations(surfaceAnimator);
clearInvocations(surfaceFreezer);
doReturn(true).when(surfaceFreezer).hasLeash();
container.setLayer(t, 1);
container.setRelativeLayer(t, relativeParent, 2);
// Set through surfaceFreezer if surfaceFreezer has leash.
verify(surfaceFreezer).setLayer(t, 1);
verify(surfaceFreezer).setRelativeLayer(t, relativeParent, 2);
verify(surfaceAnimator, never()).setLayer(any(), anyInt());
verify(surfaceAnimator, never()).setRelativeLayer(any(), any(), anyInt());
}
@Test
public void testStartChangeTransitionWhenPreviousIsNotFinished() {
final WindowContainer container = createTaskFragmentWithParentTask(
createTask(mDisplayContent), false);
container.mSurfaceControl = mock(SurfaceControl.class);
final SurfaceAnimator surfaceAnimator = container.mSurfaceAnimator;
final SurfaceFreezer surfaceFreezer = container.mSurfaceFreezer;
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
spyOn(container);
spyOn(surfaceAnimator);
mockSurfaceFreezerSnapshot(surfaceFreezer);
doReturn(t).when(container).getPendingTransaction();
doReturn(t).when(container).getSyncTransaction();
// Leash and snapshot created for change transition.
container.initializeChangeTransition(new Rect(0, 0, 1000, 2000));
assertNotNull(surfaceFreezer.mLeash);
assertNotNull(surfaceFreezer.mSnapshot);
assertEquals(surfaceFreezer.mLeash, container.getAnimationLeash());
// Start animation: surfaceAnimator take over the leash and snapshot from surfaceFreezer.
container.applyAnimationUnchecked(null /* lp */, true /* enter */,
TRANSIT_OLD_TASK_FRAGMENT_CHANGE, false /* isVoiceInteraction */,
null /* sources */);
assertNull(surfaceFreezer.mLeash);
assertNull(surfaceFreezer.mSnapshot);
assertNotNull(surfaceAnimator.mLeash);
assertNotNull(surfaceAnimator.mSnapshot);
final SurfaceControl prevLeash = surfaceAnimator.mLeash;
final SurfaceFreezer.Snapshot prevSnapshot = surfaceAnimator.mSnapshot;
// Prepare another change transition.
container.initializeChangeTransition(new Rect(0, 0, 1000, 2000));
assertNotNull(surfaceFreezer.mLeash);
assertNotNull(surfaceFreezer.mSnapshot);
assertEquals(surfaceFreezer.mLeash, container.getAnimationLeash());
assertNotEquals(prevLeash, container.getAnimationLeash());
// Start another animation before the previous one is finished, it should reset the previous
// one, but not change the current one.
container.applyAnimationUnchecked(null /* lp */, true /* enter */,
TRANSIT_OLD_TASK_FRAGMENT_CHANGE, false /* isVoiceInteraction */,
null /* sources */);
verify(container, never()).onAnimationLeashLost(any());
verify(surfaceFreezer, never()).unfreeze(any());
assertNotNull(surfaceAnimator.mLeash);
assertNotNull(surfaceAnimator.mSnapshot);
assertEquals(surfaceAnimator.mLeash, container.getAnimationLeash());
assertNotEquals(prevLeash, surfaceAnimator.mLeash);
assertNotEquals(prevSnapshot, surfaceAnimator.mSnapshot);
// Clean up after animation finished.
surfaceAnimator.mInnerAnimationFinishedCallback.onAnimationFinished(
ANIMATION_TYPE_APP_TRANSITION, surfaceAnimator.getAnimation());
verify(container).onAnimationLeashLost(any());
assertNull(surfaceAnimator.mLeash);
assertNull(surfaceAnimator.mSnapshot);
}
@Test
public void testUnfreezeWindow_removeWindowFromChanging() {
final WindowContainer container = createTaskFragmentWithParentTask(
createTask(mDisplayContent), false);
mockSurfaceFreezerSnapshot(container.mSurfaceFreezer);
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
container.initializeChangeTransition(new Rect(0, 0, 1000, 2000));
assertTrue(mDisplayContent.mChangingContainers.contains(container));
container.mSurfaceFreezer.unfreeze(t);
assertFalse(mDisplayContent.mChangingContainers.contains(container));
}
@Test
public void testFailToTaskSnapshot_unfreezeWindow() {
final WindowContainer container = createTaskFragmentWithParentTask(
createTask(mDisplayContent), false);
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
spyOn(container.mSurfaceFreezer);
container.initializeChangeTransition(new Rect(0, 0, 1000, 2000));
verify(container.mSurfaceFreezer).freeze(any(), any(), any(), any());
verify(container.mSurfaceFreezer).unfreeze(any());
assertTrue(mDisplayContent.mChangingContainers.isEmpty());
}
@Test
public void testRemoveUnstartedFreezeSurfaceWhenFreezeAgain() {
final WindowContainer container = createTaskFragmentWithParentTask(
createTask(mDisplayContent), false);
container.mSurfaceControl = mock(SurfaceControl.class);
final SurfaceFreezer surfaceFreezer = container.mSurfaceFreezer;
mockSurfaceFreezerSnapshot(surfaceFreezer);
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
spyOn(container);
doReturn(t).when(container).getPendingTransaction();
doReturn(t).when(container).getSyncTransaction();
// Leash and snapshot created for change transition.
container.initializeChangeTransition(new Rect(0, 0, 1000, 2000));
assertNotNull(surfaceFreezer.mLeash);
assertNotNull(surfaceFreezer.mSnapshot);
final SurfaceControl prevLeash = surfaceFreezer.mLeash;
final SurfaceFreezer.Snapshot prevSnapshot = surfaceFreezer.mSnapshot;
spyOn(prevSnapshot);
container.initializeChangeTransition(new Rect(0, 0, 1500, 2500));
verify(t).remove(prevLeash);
verify(prevSnapshot).destroy(t);
}
@Test
public void testAddLocalInsetsSourceProvider() {
/*
___ rootTask _______________________________________________
| | | |
activity0 container navigationBarInsetsProvider1 navigationBarInsetsProvider2
/ \
activity1 activity2
*/
final Task rootTask = createTask(mDisplayContent);
final ActivityRecord activity0 = createActivityRecord(mDisplayContent,
createTaskInRootTask(rootTask, 0 /* userId */));
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
TYPE_BASE_APPLICATION);
attrs.setTitle("AppWindow0");
activity0.addWindow(createWindowState(attrs, activity0));
final Task container = createTaskInRootTask(rootTask, 0);
final ActivityRecord activity1 = createActivityRecord(mDisplayContent,
createTaskInRootTask(container, 0 /* userId */));
final WindowManager.LayoutParams attrs1 = new WindowManager.LayoutParams(
TYPE_BASE_APPLICATION);
attrs1.setTitle("AppWindow1");
activity1.addWindow(createWindowState(attrs1, activity1));
final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
createTaskInRootTask(container, 0 /* userId */));
final WindowManager.LayoutParams attrs2 = new WindowManager.LayoutParams(
TYPE_BASE_APPLICATION);
attrs2.setTitle("AppWindow2");
activity2.addWindow(createWindowState(attrs2, activity2));
Rect navigationBarInsetsRect1 = new Rect(0, 200, 1080, 700);
Rect navigationBarInsetsRect2 = new Rect(0, 0, 1080, 200);
rootTask.addLocalRectInsetsSourceProvider(navigationBarInsetsRect1,
new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
container.addLocalRectInsetsSourceProvider(navigationBarInsetsRect2,
new int[]{ITYPE_LOCAL_NAVIGATION_BAR_2});
InsetsSource navigationBarInsetsProvider1Source = new InsetsSource(
ITYPE_LOCAL_NAVIGATION_BAR_1);
navigationBarInsetsProvider1Source.setFrame(navigationBarInsetsRect1);
navigationBarInsetsProvider1Source.setVisible(true);
InsetsSource navigationBarInsetsProvider2Source = new InsetsSource(
ITYPE_LOCAL_NAVIGATION_BAR_2);
navigationBarInsetsProvider2Source.setFrame(navigationBarInsetsRect2);
navigationBarInsetsProvider2Source.setVisible(true);
activity0.forAllWindows(window -> {
assertEquals(navigationBarInsetsRect1,
window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame());
assertEquals(null,
window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2));
}, true);
activity1.forAllWindows(window -> {
assertEquals(navigationBarInsetsRect1,
window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame());
assertEquals(navigationBarInsetsRect2,
window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2).getFrame());
}, true);
activity2.forAllWindows(window -> {
assertEquals(navigationBarInsetsRect1,
window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame());
assertEquals(navigationBarInsetsRect2,
window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2).getFrame());
}, true);
}
@Test
public void testAddLocalInsetsSourceProvider_sameType_replacesInsets() {
/*
___ rootTask ________________________________________
| | |
activity0 navigationBarInsetsProvider1 navigationBarInsetsProvider2
*/
final Task rootTask = createTask(mDisplayContent);
final ActivityRecord activity0 = createActivityRecord(mDisplayContent,
createTaskInRootTask(rootTask, 0 /* userId */));
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
TYPE_BASE_APPLICATION);
attrs.setTitle("AppWindow0");
activity0.addWindow(createWindowState(attrs, activity0));
Rect navigationBarInsetsRect1 = new Rect(0, 200, 1080, 700);
Rect navigationBarInsetsRect2 = new Rect(0, 0, 1080, 200);
rootTask.addLocalRectInsetsSourceProvider(navigationBarInsetsRect1,
new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
activity0.forAllWindows(window -> {
assertEquals(navigationBarInsetsRect1,
window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame());
}, true);
rootTask.addLocalRectInsetsSourceProvider(navigationBarInsetsRect2,
new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
activity0.forAllWindows(window -> {
assertEquals(navigationBarInsetsRect2,
window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame());
}, true);
}
@Test
public void testRemoveLocalInsetsSourceProvider() {
/*
___ rootTask _______________________________________________
| | | |
activity0 container navigationBarInsetsProvider1 navigationBarInsetsProvider2
/ \
activity1 activity2
*/
final Task rootTask = createTask(mDisplayContent);
final ActivityRecord activity0 = createActivityRecord(mDisplayContent,
createTaskInRootTask(rootTask, 0 /* userId */));
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
TYPE_BASE_APPLICATION);
attrs.setTitle("AppWindow0");
activity0.addWindow(createWindowState(attrs, activity0));
final Task container = createTaskInRootTask(rootTask, 0);
final ActivityRecord activity1 = createActivityRecord(mDisplayContent,
createTaskInRootTask(container, 0 /* userId */));
final WindowManager.LayoutParams attrs1 = new WindowManager.LayoutParams(
TYPE_BASE_APPLICATION);
attrs1.setTitle("AppWindow1");
activity1.addWindow(createWindowState(attrs1, activity1));
final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
createTaskInRootTask(container, 0 /* userId */));
final WindowManager.LayoutParams attrs2 = new WindowManager.LayoutParams(
TYPE_BASE_APPLICATION);
attrs2.setTitle("AppWindow2");
activity2.addWindow(createWindowState(attrs2, activity2));
activity2.addWindow(createWindowState(attrs2, activity2));
Rect navigationBarInsetsRect1 = new Rect(0, 200, 1080, 700);
Rect navigationBarInsetsRect2 = new Rect(0, 0, 1080, 200);
rootTask.addLocalRectInsetsSourceProvider(navigationBarInsetsRect1,
new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
container.addLocalRectInsetsSourceProvider(navigationBarInsetsRect2,
new int[]{ITYPE_LOCAL_NAVIGATION_BAR_2});
mDisplayContent.getInsetsStateController().onPostLayout();
rootTask.removeLocalInsetsSourceProvider(new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
mDisplayContent.getInsetsStateController().onPostLayout();
activity0.forAllWindows(window -> {
assertEquals(null,
window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1));
assertEquals(null,
window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2));
}, true);
activity1.forAllWindows(window -> {
assertEquals(null,
window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1));
assertEquals(navigationBarInsetsRect2,
window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2).getFrame());
}, true);
activity2.forAllWindows(window -> {
assertEquals(null,
window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1));
assertEquals(navigationBarInsetsRect2,
window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2).getFrame());
}, true);
}
/* Used so we can gain access to some protected members of the {@link WindowContainer} class */
private static class TestWindowContainer extends WindowContainer<TestWindowContainer> {
private final int mLayer;
private boolean mIsAnimating;
private boolean mIsVisible;
private boolean mFillsParent;
private boolean mWaitForTransitStart;
private Integer mOrientation;
private boolean mOnParentChangedCalled;
private boolean mOnDescendantOverrideCalled;
/**
* Compares 2 window layers and returns -1 if the first is lesser than the second in terms
* of z-order and 1 otherwise.
*/
private static final Comparator<TestWindowContainer> SUBLAYER_COMPARATOR = (w1, w2) -> {
final int layer1 = w1.mLayer;
final int layer2 = w2.mLayer;
if (layer1 < layer2 || (layer1 == layer2 && layer2 < 0)) {
// We insert the child window into the list ordered by the mLayer. For same layers,
// the negative one should go below others; the positive one should go above others.
return -1;
}
if (layer1 == layer2) return 0;
return 1;
};
TestWindowContainer(WindowManagerService wm, int layer, boolean isAnimating,
boolean isVisible, boolean waitTransitStart, Integer orientation) {
super(wm);
mLayer = layer;
mIsAnimating = isAnimating;
mIsVisible = isVisible;
mFillsParent = true;
mOrientation = orientation;
mWaitForTransitStart = waitTransitStart;
spyOn(mSurfaceAnimator);
doReturn(mIsAnimating).when(mSurfaceAnimator).isAnimating();
doReturn(ANIMATION_TYPE_APP_TRANSITION).when(mSurfaceAnimator).getAnimationType();
}
TestWindowContainer getParentWindow() {
return (TestWindowContainer) getParent();
}
int getChildrenCount() {
return mChildren.size();
}
TestWindowContainer addChildWindow(TestWindowContainer child) {
addChild(child, SUBLAYER_COMPARATOR);
return child;
}
TestWindowContainer addChildWindow(TestWindowContainerBuilder childBuilder) {
TestWindowContainer child = childBuilder.build();
addChild(child, SUBLAYER_COMPARATOR);
return child;
}
TestWindowContainer addChildWindow() {
return addChildWindow(new TestWindowContainerBuilder(mWmService).setLayer(1));
}
@Override
void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
mOnParentChangedCalled = true;
}
@Override
void onDescendantOverrideConfigurationChanged() {
mOnDescendantOverrideCalled = true;
super.onDescendantOverrideConfigurationChanged();
}
@Override
boolean isVisible() {
return mIsVisible;
}
@Override
int getOrientation(int candidate) {
return mOrientation != null ? mOrientation : super.getOrientation(candidate);
}
@Override
int getOrientation() {
return getOrientation(super.mOrientation);
}
@Override
boolean fillsParent() {
return mFillsParent;
}
void setFillsParent(boolean fillsParent) {
mFillsParent = fillsParent;
}
@Override
boolean isWaitingForTransitionStart() {
return mWaitForTransitStart;
}
}
private static class TestWindowContainerBuilder {
private final WindowManagerService mWm;
private int mLayer;
private boolean mIsAnimating;
private boolean mIsVisible;
private boolean mIsWaitTransitStart;
private Integer mOrientation;
TestWindowContainerBuilder(WindowManagerService wm) {
mWm = wm;
mLayer = 0;
mIsAnimating = false;
mIsVisible = false;
mOrientation = null;
}
TestWindowContainerBuilder setLayer(int layer) {
mLayer = layer;
return this;
}
TestWindowContainerBuilder setIsAnimating(boolean isAnimating) {
mIsAnimating = isAnimating;
return this;
}
TestWindowContainerBuilder setIsVisible(boolean isVisible) {
mIsVisible = isVisible;
return this;
}
TestWindowContainerBuilder setOrientation(int orientation) {
mOrientation = orientation;
return this;
}
TestWindowContainerBuilder setWaitForTransitionStart(boolean waitTransitStart) {
mIsWaitTransitStart = waitTransitStart;
return this;
}
TestWindowContainer build() {
return new TestWindowContainer(mWm, mLayer, mIsAnimating, mIsVisible,
mIsWaitTransitStart, mOrientation);
}
}
private static class MockSurfaceBuildingContainer extends WindowContainer<WindowContainer>
implements AutoCloseable {
private final SurfaceSession mSession = new SurfaceSession();
MockSurfaceBuildingContainer(WindowManagerService wm) {
super(wm);
}
static class MockSurfaceBuilder extends SurfaceControl.Builder {
MockSurfaceBuilder(SurfaceSession ss) {
super(ss);
}
@Override
public SurfaceControl build() {
return mock(SurfaceControl.class);
}
}
@Override
SurfaceControl.Builder makeChildSurface(WindowContainer child) {
return new MockSurfaceBuilder(mSession);
}
@Override
public void close() {
mSession.kill();
}
}
private static class TestWindowContainerListener implements WindowContainerListener {
private Configuration mConfiguration = new Configuration();
private DisplayContent mDisplayContent;
@Override
public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
mConfiguration.setTo(overrideConfiguration);
}
@Override
public void onDisplayChanged(DisplayContent dc) {
mDisplayContent = dc;
}
}
}