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