• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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;
18 
19 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
20 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
21 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
22 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
23 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
24 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
25 import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
26 import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
27 import static android.view.RoundedCorner.POSITION_TOP_LEFT;
28 import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
29 import static android.view.Surface.ROTATION_0;
30 import static android.view.Surface.ROTATION_180;
31 import static android.view.Surface.ROTATION_270;
32 import static android.view.Surface.ROTATION_90;
33 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
34 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
35 
36 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
37 
38 import static org.junit.Assert.assertEquals;
39 import static org.junit.Assert.assertNull;
40 
41 import android.app.Activity;
42 import android.content.Intent;
43 import android.graphics.Rect;
44 import android.os.Bundle;
45 import android.platform.test.annotations.Presubmit;
46 import android.util.Log;
47 import android.view.Display;
48 import android.view.Gravity;
49 import android.view.RoundedCorner;
50 import android.view.View;
51 import android.view.Window;
52 import android.view.WindowInsets;
53 import android.view.WindowManager;
54 import android.view.WindowMetrics;
55 
56 import androidx.annotation.NonNull;
57 import androidx.test.rule.ActivityTestRule;
58 
59 import com.android.compatibility.common.util.PollingCheck;
60 
61 import org.junit.After;
62 import org.junit.Rule;
63 import org.junit.Test;
64 import org.junit.runner.RunWith;
65 import org.junit.runners.Parameterized;
66 
67 @Presubmit
68 @RunWith(Parameterized.class)
69 public class RoundedCornerTests extends ActivityManagerTestBase {
70     private static final String TAG = "RoundedCornerTests";
71     private static final int POSITION_LENGTH = 4;
72     private static final long TIMEOUT_IN_MILLISECONDS = 1000;
73 
74     @Parameterized.Parameters(name= "{1}({0})")
data()75     public static Object[][] data() {
76         return new Object[][]{
77                 {SCREEN_ORIENTATION_PORTRAIT, "SCREEN_ORIENTATION_PORTRAIT"},
78                 {SCREEN_ORIENTATION_LANDSCAPE, "SCREEN_ORIENTATION_LANDSCAPE"},
79                 {SCREEN_ORIENTATION_REVERSE_LANDSCAPE, "SCREEN_ORIENTATION_REVERSE_LANDSCAPE"},
80                 {SCREEN_ORIENTATION_REVERSE_PORTRAIT, "SCREEN_ORIENTATION_REVERSE_PORTRAIT"},
81         };
82     }
83 
84     @Parameterized.Parameter(0)
85     public int orientation;
86 
87     @Parameterized.Parameter(1)
88     public String orientationName;
89 
90     private final WindowManagerStateHelper mWindowManagerStateHelper =
91             new WindowManagerStateHelper();
92 
93     @After
tearDown()94     public void tearDown() {
95         mTestActivityRule.finishActivity();
96         mWindowManagerStateHelper.waitForDisplayUnfrozen();
97     }
98 
99     @Rule
100     public final ActivityTestRule<TestActivity> mTestActivityRule =
101             new ActivityTestRule<>(TestActivity.class, false /* initialTouchMode */,
102                     false /* launchActivity */);
103 
104     @Test
testRoundedCorner_fullscreen()105     public void testRoundedCorner_fullscreen() {
106         verifyRoundedCorners(false /* excludeRoundedCorners */);
107     }
108 
109     @Test
testRoundedCorner_excludeRoundedCorners()110     public void testRoundedCorner_excludeRoundedCorners() {
111         verifyRoundedCorners(true /* excludeRoundedCorners */);
112     }
113 
verifyRoundedCorners(boolean excludedRoundedCorners)114     private void verifyRoundedCorners(boolean excludedRoundedCorners) {
115         final TestActivity activity = mTestActivityRule.launchActivity(new Intent());
116 
117         if (excludedRoundedCorners && !activity.hasRoundedCorners()) {
118             Log.d(TAG, "There is no rounded corner on the display. Skipped!!");
119             return;
120         }
121 
122         waitAndAssertResumedActivity(activity.getComponentName(), "Activity must be resumed.");
123 
124         int rotation = getRotation(activity, orientation);
125 
126         if (rotation != ROTATION_0) {
127             // If the device doesn't support rotation, just verify the rounded corner with
128             // the current orientation.
129             if (!supportsRotation()) {
130                 return;
131             }
132             RotationSession rotationSession = createManagedRotationSession();
133             rotationSession.set(rotation);
134 
135             mInstrumentation.getUiAutomation().syncInputTransactions();
136         }
137 
138         runOnMainSync(() -> activity.addChildWindow(
139                 activity.calculateWindowBounds(excludedRoundedCorners)));
140         try {
141             // Make sure the child window has been laid out.
142             PollingCheck.waitFor(TIMEOUT_IN_MILLISECONDS,
143                     () -> activity.getDispatchedInsets() != null);
144             final WindowInsets insets = activity.getDispatchedInsets();
145 
146             if (excludedRoundedCorners) {
147                 for (int i = 0; i < POSITION_LENGTH; i++) {
148                     assertNull("The rounded corners should be null.",
149                             insets.getRoundedCorner(i));
150                 }
151             } else {
152                 final Display display = activity.getDisplay();
153                 for (int j = 0; j < POSITION_LENGTH; j++) {
154                     assertEquals(insets.getRoundedCorner(j), display.getRoundedCorner(j));
155                 }
156             }
157         } finally {
158             runOnMainSync(activity::removeChildWindow);
159         }
160     }
161 
162     /**
163      * Returns the rotation based on {@code orientations}.
164      */
getRotation(@onNull Activity activity, int requestedOrientation)165     private static int getRotation(@NonNull Activity activity, int requestedOrientation) {
166         // Not use Activity#getRequestedOrientation because the possible values are dozens and hard
167         // to determine the rotation.
168         int currentOrientation = activity.getResources().getConfiguration().orientation;
169         if (currentOrientation == ORIENTATION_PORTRAIT) {
170             switch (requestedOrientation) {
171                 case SCREEN_ORIENTATION_PORTRAIT: {
172                     return ROTATION_0;
173                 }
174                 case SCREEN_ORIENTATION_LANDSCAPE: {
175                     return ROTATION_90;
176                 }
177                 case SCREEN_ORIENTATION_REVERSE_PORTRAIT: {
178                     return ROTATION_180;
179                 }
180                 case SCREEN_ORIENTATION_REVERSE_LANDSCAPE: {
181                     return ROTATION_270;
182                 }
183             }
184         } else {
185             switch (requestedOrientation) {
186                 case SCREEN_ORIENTATION_PORTRAIT: {
187                     return ROTATION_90;
188                 }
189                 case SCREEN_ORIENTATION_LANDSCAPE: {
190                     return ROTATION_0;
191                 }
192                 case SCREEN_ORIENTATION_REVERSE_PORTRAIT: {
193                     return ROTATION_270;
194                 }
195                 case SCREEN_ORIENTATION_REVERSE_LANDSCAPE: {
196                     return ROTATION_180;
197                 }
198             }
199         }
200         throw new IllegalArgumentException("Unknown orientation value:" + requestedOrientation);
201     }
202 
runOnMainSync(Runnable runnable)203     private void runOnMainSync(Runnable runnable) {
204         getInstrumentation().runOnMainSync(runnable);
205     }
206 
207     public static class TestActivity extends Activity {
208         static final String EXTRA_ORIENTATION = "extra.orientation";
209 
210         private View mChildWindowRoot;
211         private WindowInsets mDispatchedInsets;
212 
213         @Override
onCreate(Bundle savedInstanceState)214         protected void onCreate(Bundle savedInstanceState) {
215             super.onCreate(savedInstanceState);
216             getWindow().requestFeature(Window.FEATURE_NO_TITLE);
217             getWindow().getAttributes().layoutInDisplayCutoutMode =
218                     LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
219             getWindow().getDecorView().getWindowInsetsController().hide(
220                     WindowInsets.Type.systemBars());
221             if (getIntent() != null) {
222                 setRequestedOrientation(getIntent().getIntExtra(
223                         EXTRA_ORIENTATION, SCREEN_ORIENTATION_UNSPECIFIED));
224             }
225         }
226 
addChildWindow(Rect bounds)227         void addChildWindow(Rect bounds) {
228             final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
229             attrs.x = bounds.left;
230             attrs.y = bounds.top;
231             attrs.width = bounds.width();
232             attrs.height = bounds.height();
233             attrs.gravity = Gravity.LEFT | Gravity.TOP;
234             attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
235             attrs.flags = FLAG_NOT_FOCUSABLE;
236             attrs.setFitInsetsTypes(0);
237             mChildWindowRoot = new View(this);
238             mChildWindowRoot.setOnApplyWindowInsetsListener(
239                     (v, insets) -> mDispatchedInsets = insets);
240             getWindowManager().addView(mChildWindowRoot, attrs);
241         }
242 
removeChildWindow()243         void removeChildWindow() {
244             if (mChildWindowRoot != null) {
245                 getWindowManager().removeViewImmediate(mChildWindowRoot);
246             }
247         }
248 
getDispatchedInsets()249         WindowInsets getDispatchedInsets() {
250             return mDispatchedInsets;
251         }
252 
hasRoundedCorners()253         boolean hasRoundedCorners() {
254             final Display display = getDisplay();
255             return display.getRoundedCorner(POSITION_TOP_LEFT) != null
256                     || display.getRoundedCorner(POSITION_TOP_RIGHT) != null
257                     || display.getRoundedCorner(POSITION_BOTTOM_RIGHT) != null
258                     || display.getRoundedCorner(POSITION_BOTTOM_LEFT) != null;
259         }
260 
calculateWindowBounds(boolean excludeRoundedCorners)261         Rect calculateWindowBounds(boolean excludeRoundedCorners) {
262             final Display display = getDisplay();
263             final WindowMetrics windowMetrics = getWindowManager().getCurrentWindowMetrics();
264             if (!excludeRoundedCorners) {
265                 return windowMetrics.getBounds();
266             }
267             final Rect bounds = new Rect();
268             final int width = windowMetrics.getBounds().width();
269             final int height = windowMetrics.getBounds().height();
270             final RoundedCorner topLeft = display.getRoundedCorner(POSITION_TOP_LEFT);
271             final RoundedCorner topRight = display.getRoundedCorner(POSITION_TOP_RIGHT);
272             final RoundedCorner bottomRight = display.getRoundedCorner(POSITION_BOTTOM_RIGHT);
273             final RoundedCorner bottomLeft = display.getRoundedCorner(POSITION_BOTTOM_LEFT);
274 
275             bounds.left = Math.max(topLeft != null ? topLeft.getCenter().x : 0,
276                     bottomLeft != null ? bottomLeft.getCenter().x : 0);
277             bounds.top = Math.max(topLeft != null ? topLeft.getCenter().y : 0,
278                     bottomLeft != null ? bottomLeft.getCenter().y : 0);
279             bounds.right = Math.min(topRight != null ? topRight.getCenter().x : width,
280                     bottomRight != null ? bottomRight.getCenter().x : width);
281             bounds.bottom = Math.min(bottomRight != null ? bottomRight.getCenter().y : height,
282                     bottomLeft != null ? bottomLeft.getCenter().y : height);
283 
284             Log.d(TAG, "Window bounds with rounded corners excluded = " + bounds);
285             return bounds;
286         }
287     }
288 }
289