1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.server.wm.jetpack; 18 19 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; 20 import static android.content.res.Configuration.ORIENTATION_PORTRAIT; 21 import static android.server.wm.jetpack.extensions.util.SidecarUtil.assertEqualWindowLayoutInfo; 22 import static android.server.wm.jetpack.extensions.util.SidecarUtil.assumeHasDisplayFeatures; 23 import static android.server.wm.jetpack.extensions.util.SidecarUtil.assumeSidecarSupportedDevice; 24 import static android.server.wm.jetpack.extensions.util.SidecarUtil.getSidecarInterface; 25 26 import static com.google.common.truth.Truth.assertThat; 27 28 import static org.junit.Assert.assertEquals; 29 import static org.junit.Assert.assertTrue; 30 31 import android.graphics.Rect; 32 import android.os.IBinder; 33 import android.platform.test.annotations.Presubmit; 34 import android.server.wm.SetRequestedOrientationRule; 35 import android.server.wm.jetpack.utils.SidecarCallbackCounter; 36 import android.server.wm.jetpack.utils.TestActivity; 37 import android.server.wm.jetpack.utils.TestConfigChangeHandlingActivity; 38 import android.server.wm.jetpack.utils.TestGetWindowLayoutInfoActivity; 39 import android.server.wm.jetpack.utils.WindowManagerJetpackTestBase; 40 41 import androidx.test.ext.junit.runners.AndroidJUnit4; 42 import androidx.test.filters.LargeTest; 43 import androidx.window.sidecar.SidecarDeviceState; 44 import androidx.window.sidecar.SidecarDisplayFeature; 45 import androidx.window.sidecar.SidecarInterface; 46 import androidx.window.sidecar.SidecarWindowLayoutInfo; 47 48 import com.google.common.collect.BoundType; 49 import com.google.common.collect.Range; 50 51 import org.junit.Before; 52 import org.junit.ClassRule; 53 import org.junit.Test; 54 import org.junit.runner.RunWith; 55 56 /** 57 * Tests for the {@link androidx.window.sidecar} implementation provided on the device (and only 58 * if one is available). 59 * 60 * Build/Install/Run: 61 * atest CtsWindowManagerJetpackTestCases:SidecarTest 62 */ 63 @Presubmit 64 @LargeTest 65 @RunWith(AndroidJUnit4.class) 66 public class SidecarTest extends WindowManagerJetpackTestBase { 67 private static final String TAG = "SidecarTest"; 68 69 private TestActivity mActivity; 70 private SidecarInterface mSidecarInterface; 71 private IBinder mWindowToken; 72 73 // To disable special handling which prevents setRequestedOrientation from changing the screen 74 // rotation for large screen devices. 75 @ClassRule 76 public static final SetRequestedOrientationRule sSetRequestedOrientationRule = 77 new SetRequestedOrientationRule(); 78 79 @Before 80 @Override setUp()81 public void setUp() throws Exception { 82 super.setUp(); 83 assumeSidecarSupportedDevice(mContext); 84 mActivity = startFullScreenActivityNewTask(TestActivity.class, null); 85 mSidecarInterface = getSidecarInterface(mActivity); 86 assertThat(mSidecarInterface).isNotNull(); 87 mWindowToken = getActivityWindowToken(mActivity); 88 assertThat(mWindowToken).isNotNull(); 89 } 90 91 /** 92 * Test adding and removing a sidecar interface window layout change listener. 93 */ 94 @Test testSidecarInterface_onWindowLayoutChangeListener()95 public void testSidecarInterface_onWindowLayoutChangeListener() { 96 // Set activity to portrait 97 setActivityOrientationActivityDoesNotHandleOrientationChanges(mActivity, 98 ORIENTATION_PORTRAIT); 99 100 // Create the sidecar callback. onWindowLayoutChanged should only be called twice in this 101 // test, not the third time when the orientation will change because the listener will be 102 // removed. 103 SidecarCallbackCounter sidecarCallback = new SidecarCallbackCounter(mWindowToken); 104 mSidecarInterface.setSidecarCallback(sidecarCallback); 105 106 // Add window layout listener for mWindowToken - onWindowLayoutChanged should be called 107 mSidecarInterface.onWindowLayoutChangeListenerAdded(mWindowToken); 108 109 // Change the activity orientation - onWindowLayoutChanged should be called 110 setActivityOrientationActivityDoesNotHandleOrientationChanges(mActivity, 111 ORIENTATION_LANDSCAPE); 112 113 // Check that the callback is called at least twice 114 // The callback could be called more than twice because there may have additional 115 // configuration changes on some device configurations. 116 assertTrue("Callback should be called twice", sidecarCallback.getCallbackCount() >= 2); 117 118 // Reset the callback count 119 sidecarCallback.resetCallbackCount(); 120 121 // Remove the listener 122 mSidecarInterface.onWindowLayoutChangeListenerRemoved(mWindowToken); 123 124 // Change the activity orientation - onWindowLayoutChanged should NOT be called 125 setActivityOrientationActivityDoesNotHandleOrientationChanges(mActivity, 126 ORIENTATION_PORTRAIT); 127 128 // Check that the callback should not be called 129 assertEquals("Callback should not be called", 0, sidecarCallback.getCallbackCount()); 130 } 131 132 @Test testSidecarInterface_getWindowLayoutInfo()133 public void testSidecarInterface_getWindowLayoutInfo() { 134 assumeHasDisplayFeatures(mSidecarInterface, mWindowToken); 135 136 // Layout must happen after launch 137 assertThat(mActivity.waitForLayout()).isTrue(); 138 SidecarWindowLayoutInfo windowLayoutInfo = mSidecarInterface.getWindowLayoutInfo( 139 mWindowToken); 140 assertThat(windowLayoutInfo).isNotNull(); 141 142 for (SidecarDisplayFeature displayFeature : windowLayoutInfo.displayFeatures) { 143 int featureType = displayFeature.getType(); 144 assertThat(featureType).isAtLeast(SidecarDisplayFeature.TYPE_FOLD); 145 assertThat(featureType).isAtMost(SidecarDisplayFeature.TYPE_HINGE); 146 147 Rect featureRect = displayFeature.getRect(); 148 // Feature cannot have negative area 149 assertHasNonNegativeDimensions(featureRect); 150 // Feature cannot have zero width and height, at most only one dimension can be zero 151 assertNotBothDimensionsZero(featureRect); 152 // Check that feature is within the activity bounds 153 assertTrue(getActivityBounds(mActivity).contains(featureRect)); 154 } 155 } 156 157 @Test testSidecarInterface_getDeviceState()158 public void testSidecarInterface_getDeviceState() { 159 SidecarDeviceState deviceState = mSidecarInterface.getDeviceState(); 160 assertThat(deviceState).isNotNull(); 161 162 assertThat(deviceState.posture).isIn(Range.range( 163 SidecarDeviceState.POSTURE_UNKNOWN, BoundType.CLOSED, 164 SidecarDeviceState.POSTURE_FLIPPED, BoundType.CLOSED)); 165 } 166 167 @Test testSidecarInterface_onDeviceStateListenersChanged()168 public void testSidecarInterface_onDeviceStateListenersChanged() { 169 SidecarDeviceState deviceState1 = mSidecarInterface.getDeviceState(); 170 mSidecarInterface.onDeviceStateListenersChanged(false /* isEmpty */); 171 SidecarDeviceState deviceState2 = mSidecarInterface.getDeviceState(); 172 mSidecarInterface.onDeviceStateListenersChanged(true /* isEmpty */); 173 SidecarDeviceState deviceState3 = mSidecarInterface.getDeviceState(); 174 175 assertEquals(deviceState1.posture, deviceState2.posture); 176 assertEquals(deviceState1.posture, deviceState3.posture); 177 } 178 179 /** 180 * Tests that before an activity is attached to a window, 181 * {@link SidecarInterface#getWindowLayoutInfo()} either returns the same value as it would 182 * after the activity is attached to a window or throws an exception. 183 */ 184 @Test testGetWindowLayoutInfo_activityNotAttachedToWindow_returnsCorrectValue()185 public void testGetWindowLayoutInfo_activityNotAttachedToWindow_returnsCorrectValue() { 186 assumeHasDisplayFeatures(mSidecarInterface, mWindowToken); 187 188 // The value is verified inside TestGetWindowLayoutInfoActivity 189 TestGetWindowLayoutInfoActivity.resetResumeCounter(); 190 TestGetWindowLayoutInfoActivity testGetWindowLayoutInfoActivity = startActivityNewTask( 191 TestGetWindowLayoutInfoActivity.class); 192 193 // Make sure the activity has gone through all states. 194 assertThat(TestGetWindowLayoutInfoActivity.waitForOnResume()).isTrue(); 195 assertThat(testGetWindowLayoutInfoActivity.waitForLayout()).isTrue(); 196 } 197 198 @Test testGetWindowLayoutInfo_configChanged_windowLayoutUpdates()199 public void testGetWindowLayoutInfo_configChanged_windowLayoutUpdates() { 200 assumeHasDisplayFeatures(mSidecarInterface, mWindowToken); 201 202 TestConfigChangeHandlingActivity configHandlingActivity = startActivityNewTask( 203 TestConfigChangeHandlingActivity.class); 204 SidecarInterface sidecar = getSidecarInterface(configHandlingActivity); 205 assertThat(sidecar).isNotNull(); 206 IBinder configHandlingActivityWindowToken = getActivityWindowToken(configHandlingActivity); 207 assertThat(configHandlingActivityWindowToken).isNotNull(); 208 209 setActivityOrientationActivityHandlesOrientationChanges(configHandlingActivity, 210 ORIENTATION_PORTRAIT); 211 SidecarWindowLayoutInfo portraitWindowLayoutInfo = 212 sidecar.getWindowLayoutInfo(configHandlingActivityWindowToken); 213 final Rect portraitBounds = getActivityBounds(configHandlingActivity); 214 final Rect portraitMaximumBounds = getMaximumActivityBounds(configHandlingActivity); 215 216 setActivityOrientationActivityHandlesOrientationChanges(configHandlingActivity, 217 ORIENTATION_LANDSCAPE); 218 SidecarWindowLayoutInfo landscapeWindowLayoutInfo = 219 sidecar.getWindowLayoutInfo(configHandlingActivityWindowToken); 220 final Rect landscapeBounds = getActivityBounds(configHandlingActivity); 221 final Rect landscapeMaximumBounds = getMaximumActivityBounds(configHandlingActivity); 222 223 final boolean doesDisplayRotateForOrientation = doesDisplayRotateForOrientation( 224 portraitMaximumBounds, landscapeMaximumBounds); 225 assertEqualWindowLayoutInfo(portraitWindowLayoutInfo, landscapeWindowLayoutInfo, 226 portraitBounds, landscapeBounds, doesDisplayRotateForOrientation); 227 } 228 229 @Test testGetWindowLayoutInfo_windowRecreated_windowLayoutUpdates()230 public void testGetWindowLayoutInfo_windowRecreated_windowLayoutUpdates() { 231 assumeHasDisplayFeatures(mSidecarInterface, mWindowToken); 232 233 setActivityOrientationActivityDoesNotHandleOrientationChanges(mActivity, 234 ORIENTATION_PORTRAIT); 235 SidecarWindowLayoutInfo portraitWindowLayoutInfo = 236 mSidecarInterface.getWindowLayoutInfo(mWindowToken); 237 final Rect portraitBounds = getActivityBounds(mActivity); 238 final Rect portraitMaximumBounds = getMaximumActivityBounds(mActivity); 239 240 setActivityOrientationActivityDoesNotHandleOrientationChanges(mActivity, 241 ORIENTATION_LANDSCAPE); 242 243 mWindowToken = getActivityWindowToken(mActivity); 244 assertThat(mWindowToken).isNotNull(); 245 246 SidecarWindowLayoutInfo landscapeWindowLayoutInfo = 247 mSidecarInterface.getWindowLayoutInfo(mWindowToken); 248 final Rect landscapeBounds = getActivityBounds(mActivity); 249 final Rect landscapeMaximumBounds = getMaximumActivityBounds(mActivity); 250 251 final boolean doesDisplayRotateForOrientation = doesDisplayRotateForOrientation( 252 portraitMaximumBounds, landscapeMaximumBounds); 253 assertEqualWindowLayoutInfo(portraitWindowLayoutInfo, landscapeWindowLayoutInfo, 254 portraitBounds, landscapeBounds, doesDisplayRotateForOrientation); 255 } 256 } 257