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