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