blob: 7162a442221fd8539aa15927d457d13a8cc62ce6 [file] [log] [blame]
/*
* Copyright (C) 2020 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 android.server.wm.jetpack;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.server.wm.jetpack.utils.ExtensionUtil.assertEqualWindowLayoutInfo;
import static android.server.wm.jetpack.utils.ExtensionUtil.assumeExtensionSupportedDevice;
import static android.server.wm.jetpack.utils.ExtensionUtil.assumeHasDisplayFeatures;
import static android.server.wm.jetpack.utils.ExtensionUtil.getExtensionWindowLayoutComponent;
import static android.server.wm.jetpack.utils.ExtensionUtil.getExtensionWindowLayoutInfo;
import static android.server.wm.jetpack.utils.SidecarUtil.assumeSidecarSupportedDevice;
import static android.server.wm.jetpack.utils.SidecarUtil.getSidecarInterface;
import static androidx.window.extensions.layout.FoldingFeature.STATE_FLAT;
import static androidx.window.extensions.layout.FoldingFeature.STATE_HALF_OPENED;
import static androidx.window.extensions.layout.FoldingFeature.TYPE_FOLD;
import static androidx.window.extensions.layout.FoldingFeature.TYPE_HINGE;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeNotNull;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.server.wm.jetpack.utils.TestActivity;
import android.server.wm.jetpack.utils.TestConfigChangeHandlingActivity;
import android.server.wm.jetpack.utils.TestValueCountConsumer;
import android.server.wm.jetpack.utils.WindowManagerJetpackTestBase;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.window.extensions.layout.DisplayFeature;
import androidx.window.extensions.layout.FoldingFeature;
import androidx.window.extensions.layout.WindowLayoutComponent;
import androidx.window.extensions.layout.WindowLayoutInfo;
import androidx.window.sidecar.SidecarDisplayFeature;
import androidx.window.sidecar.SidecarInterface;
import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
/**
* Tests for the {@link androidx.window.extensions.layout.WindowLayoutComponent} implementation
* provided on the device (and only if one is available).
*
* Build/Install/Run:
* atest CtsWindowManagerJetpackTestCases:ExtensionWindowLayoutComponentTest
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
public class ExtensionWindowLayoutComponentTest extends WindowManagerJetpackTestBase {
private TestActivity mActivity;
private WindowLayoutComponent mWindowLayoutComponent;
private WindowLayoutInfo mWindowLayoutInfo;
@Before
@Override
public void setUp() {
super.setUp();
assumeExtensionSupportedDevice();
mActivity = (TestActivity) startActivityNewTask(TestActivity.class);
mWindowLayoutComponent = getExtensionWindowLayoutComponent();
assumeNotNull(mWindowLayoutComponent);
}
/**
* Test adding and removing a window layout change listener.
*/
@Test
public void testWindowLayoutComponent_onWindowLayoutChangeListener() throws Exception {
// Set activity to portrait
setActivityOrientationActivityDoesNotHandleOrientationChanges(mActivity,
ORIENTATION_PORTRAIT);
// Create the callback, onWindowLayoutChanged should only be called twice in this
// test, not the third time when the orientation will change because the listener will be
// removed.
TestValueCountConsumer<WindowLayoutInfo> windowLayoutInfoConsumer =
new TestValueCountConsumer<>();
windowLayoutInfoConsumer.setCount(2);
// Add window layout listener for mWindowToken - onWindowLayoutChanged should be called
mWindowLayoutComponent.addWindowLayoutInfoListener(mActivity, windowLayoutInfoConsumer);
// Change the activity orientation - onWindowLayoutChanged should be called
setActivityOrientationActivityDoesNotHandleOrientationChanges(mActivity,
ORIENTATION_LANDSCAPE);
// Remove the listener
mWindowLayoutComponent.removeWindowLayoutInfoListener(windowLayoutInfoConsumer);
// Change the activity orientation - onWindowLayoutChanged should NOT be called
setActivityOrientationActivityDoesNotHandleOrientationChanges(mActivity,
ORIENTATION_PORTRAIT);
// Check that the countdown is zero
WindowLayoutInfo lastValue = windowLayoutInfoConsumer.waitAndGet();
assertNotNull(lastValue);
}
@Test
public void testWindowLayoutComponent_WindowLayoutInfoListener() {
TestValueCountConsumer<WindowLayoutInfo> windowLayoutInfoConsumer =
new TestValueCountConsumer<>();
// Test that adding and removing callback succeeds
mWindowLayoutComponent.addWindowLayoutInfoListener(mActivity, windowLayoutInfoConsumer);
mWindowLayoutComponent.removeWindowLayoutInfoListener(windowLayoutInfoConsumer);
}
@Test
public void testDisplayFeatures()
throws ExecutionException, InterruptedException, TimeoutException {
mWindowLayoutInfo = getExtensionWindowLayoutInfo(mActivity);
assumeHasDisplayFeatures(mWindowLayoutInfo);
for (DisplayFeature displayFeature : mWindowLayoutInfo.getDisplayFeatures()) {
// Check that the feature bounds are valid
final Rect featureRect = displayFeature.getBounds();
// Feature cannot have negative width or height
assertHasNonNegativeDimensions(featureRect);
// The feature cannot have zero area
assertNotBothDimensionsZero(featureRect);
// The feature cannot be outside the activity bounds
assertTrue(getActivityBounds(mActivity).contains(featureRect));
if (displayFeature instanceof FoldingFeature) {
// Check that the folding feature has a valid type and state
final FoldingFeature foldingFeature = (FoldingFeature) displayFeature;
final int featureType = foldingFeature.getType();
assertThat(featureType).isIn(Range.range(
TYPE_FOLD, BoundType.CLOSED,
TYPE_HINGE, BoundType.CLOSED));
final int featureState = foldingFeature.getState();
assertThat(featureState).isIn(Range.range(
STATE_FLAT, BoundType.CLOSED,
STATE_HALF_OPENED, BoundType.CLOSED));
}
}
}
@Test
public void testGetWindowLayoutInfo_configChanged_windowLayoutUpdates()
throws ExecutionException, InterruptedException, TimeoutException {
mWindowLayoutInfo = getExtensionWindowLayoutInfo(mActivity);
assumeHasDisplayFeatures(mWindowLayoutInfo);
TestConfigChangeHandlingActivity configHandlingActivity
= (TestConfigChangeHandlingActivity) startActivityNewTask(
TestConfigChangeHandlingActivity.class);
setActivityOrientationActivityHandlesOrientationChanges(configHandlingActivity,
ORIENTATION_PORTRAIT);
final WindowLayoutInfo portraitWindowLayoutInfo = getExtensionWindowLayoutInfo(
configHandlingActivity);
final Rect portraitBounds = getActivityBounds(configHandlingActivity);
final Rect portraitMaximumBounds = getMaximumActivityBounds(configHandlingActivity);
setActivityOrientationActivityHandlesOrientationChanges(configHandlingActivity,
ORIENTATION_LANDSCAPE);
final WindowLayoutInfo landscapeWindowLayoutInfo = getExtensionWindowLayoutInfo(
configHandlingActivity);
final Rect landscapeBounds = getActivityBounds(configHandlingActivity);
final Rect landscapeMaximumBounds = getMaximumActivityBounds(configHandlingActivity);
final boolean doesDisplayRotateForOrientation = doesDisplayRotateForOrientation(
portraitMaximumBounds, landscapeMaximumBounds);
assertEqualWindowLayoutInfo(portraitWindowLayoutInfo, landscapeWindowLayoutInfo,
portraitBounds, landscapeBounds, doesDisplayRotateForOrientation);
}
@Test
public void testGetWindowLayoutInfo_windowRecreated_windowLayoutUpdates()
throws ExecutionException, InterruptedException, TimeoutException {
mWindowLayoutInfo = getExtensionWindowLayoutInfo(mActivity);
assumeHasDisplayFeatures(mWindowLayoutInfo);
setActivityOrientationActivityDoesNotHandleOrientationChanges(mActivity,
ORIENTATION_PORTRAIT);
final WindowLayoutInfo portraitWindowLayoutInfo = getExtensionWindowLayoutInfo(mActivity);
final Rect portraitBounds = getActivityBounds(mActivity);
final Rect portraitMaximumBounds = getMaximumActivityBounds(mActivity);
setActivityOrientationActivityDoesNotHandleOrientationChanges(mActivity,
ORIENTATION_LANDSCAPE);
final WindowLayoutInfo landscapeWindowLayoutInfo = getExtensionWindowLayoutInfo(mActivity);
final Rect landscapeBounds = getActivityBounds(mActivity);
final Rect landscapeMaximumBounds = getMaximumActivityBounds(mActivity);
final boolean doesDisplayRotateForOrientation = doesDisplayRotateForOrientation(
portraitMaximumBounds, landscapeMaximumBounds);
assertEqualWindowLayoutInfo(portraitWindowLayoutInfo, landscapeWindowLayoutInfo,
portraitBounds, landscapeBounds, doesDisplayRotateForOrientation);
}
/**
* Tests that if sidecar is also present, then it returns the same display features as
* extensions.
*/
@Test
public void testSidecarHasSameDisplayFeatures()
throws ExecutionException, InterruptedException, TimeoutException {
assumeSidecarSupportedDevice(mActivity);
mWindowLayoutInfo = getExtensionWindowLayoutInfo(mActivity);
assumeHasDisplayFeatures(mWindowLayoutInfo);
// Retrieve and sort the extension folding features
final List<FoldingFeature> extensionFoldingFeatures = new ArrayList<>(
mWindowLayoutInfo.getDisplayFeatures())
.stream()
.filter(d -> d instanceof FoldingFeature)
.map(d -> (FoldingFeature) d)
.collect(Collectors.toList());
// Retrieve and sort the sidecar display features in the same order as the extension
// display features
final SidecarInterface sidecarInterface = getSidecarInterface(mActivity);
final List<SidecarDisplayFeature> sidecarDisplayFeatures = sidecarInterface
.getWindowLayoutInfo(getActivityWindowToken(mActivity)).displayFeatures;
// Check that the display features are the same
assertEquals(extensionFoldingFeatures.size(), sidecarDisplayFeatures.size());
final int nFeatures = extensionFoldingFeatures.size();
if (nFeatures == 0) {
return;
}
final boolean[] extensionDisplayFeatureMatched = new boolean[nFeatures];
final boolean[] sidecarDisplayFeatureMatched = new boolean[nFeatures];
for (int extensionIndex = 0; extensionIndex < nFeatures; extensionIndex++) {
if (extensionDisplayFeatureMatched[extensionIndex]) {
// A match has already been found for this extension folding feature
continue;
}
final FoldingFeature extensionFoldingFeature = extensionFoldingFeatures
.get(extensionIndex);
for (int sidecarIndex = 0; sidecarIndex < nFeatures; sidecarIndex++) {
if (sidecarDisplayFeatureMatched[sidecarIndex]) {
// A match has already been found for this sidecar display feature
continue;
}
final SidecarDisplayFeature sidecarDisplayFeature = sidecarDisplayFeatures
.get(sidecarIndex);
// Check that the bounds, type, and state match
if (extensionFoldingFeature.getBounds().equals(sidecarDisplayFeature.getRect())
&& extensionFoldingFeature.getType() == sidecarDisplayFeature.getType()
&& areExtensionAndSidecarDeviceStateEqual(
extensionFoldingFeature.getState(),
sidecarInterface.getDeviceState().posture)) {
// Match found
extensionDisplayFeatureMatched[extensionIndex] = true;
sidecarDisplayFeatureMatched[sidecarIndex] = true;
}
}
}
// Check that a match was found for each display feature
for (int i = 0; i < nFeatures; i++) {
assertTrue(extensionDisplayFeatureMatched[i] && sidecarDisplayFeatureMatched[i]);
}
}
}