1 /* 2 * Copyright (C) 2017 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 com.android.server.wm; 18 19 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 20 import static android.graphics.Color.RED; 21 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; 22 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; 23 import static android.view.Display.DEFAULT_DISPLAY; 24 import static android.view.Gravity.BOTTOM; 25 import static android.view.Gravity.LEFT; 26 import static android.view.Gravity.RIGHT; 27 import static android.view.Gravity.TOP; 28 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 29 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; 30 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 31 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 32 import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; 33 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR; 34 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; 35 import static org.junit.Assert.assertEquals; 36 37 import android.app.Activity; 38 import android.app.ActivityOptions; 39 import android.app.Instrumentation; 40 import android.content.Context; 41 import android.content.Intent; 42 import android.graphics.PixelFormat; 43 import android.graphics.Point; 44 import android.hardware.display.DisplayManager; 45 import android.hardware.display.VirtualDisplay; 46 import android.media.ImageReader; 47 import android.os.Handler; 48 import android.platform.test.annotations.Presubmit; 49 import android.support.test.InstrumentationRegistry; 50 import android.support.test.filters.SmallTest; 51 import android.support.test.runner.AndroidJUnit4; 52 import android.util.Pair; 53 import android.view.Display; 54 import android.view.DisplayInfo; 55 import android.view.View; 56 import android.view.WindowInsets; 57 import android.view.WindowManager; 58 import android.widget.TextView; 59 60 import org.junit.After; 61 import org.junit.Assert; 62 import org.junit.Before; 63 import org.junit.Test; 64 import org.junit.runner.RunWith; 65 66 import java.util.ArrayList; 67 import java.util.function.BooleanSupplier; 68 69 /** 70 * Tests for the {@link android.view.WindowManager.LayoutParams#PRIVATE_FLAG_IS_SCREEN_DECOR} flag. 71 * 72 * Build/Install/Run: 73 * atest FrameworksServicesTests:com.android.server.wm.ScreenDecorWindowTests 74 */ 75 // TODO: Add test for FLAG_FULLSCREEN which hides the status bar and also other flags. 76 // TODO: Test non-Activity windows. 77 @SmallTest 78 @Presubmit 79 @RunWith(AndroidJUnit4.class) 80 public class ScreenDecorWindowTests { 81 82 private final Context mContext = InstrumentationRegistry.getTargetContext(); 83 private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation(); 84 85 private WindowManager mWm; 86 private ArrayList<View> mWindows = new ArrayList<>(); 87 88 private Activity mTestActivity; 89 private VirtualDisplay mDisplay; 90 private ImageReader mImageReader; 91 92 private int mDecorThickness; 93 private int mHalfDecorThickness; 94 95 @Before 96 public void setUp() { 97 final Pair<VirtualDisplay, ImageReader> result = createDisplay(); 98 mDisplay = result.first; 99 mImageReader = result.second; 100 final Display display = mDisplay.getDisplay(); 101 final Context dContext = mContext.createDisplayContext(display); 102 mWm = dContext.getSystemService(WindowManager.class); 103 mTestActivity = startActivityOnDisplay(TestActivity.class, display.getDisplayId()); 104 final Point size = new Point(); 105 mDisplay.getDisplay().getRealSize(size); 106 mDecorThickness = Math.min(size.x, size.y) / 3; 107 mHalfDecorThickness = mDecorThickness / 2; 108 } 109 110 @After 111 public void tearDown() { 112 while (!mWindows.isEmpty()) { 113 removeWindow(mWindows.get(0)); 114 } 115 finishActivity(mTestActivity); 116 mDisplay.release(); 117 mImageReader.close(); 118 } 119 120 @Test 121 public void testScreenSides() throws Exception { 122 // Decor on top 123 final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness); 124 assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness); 125 126 // Decor at the bottom 127 updateWindow(decorWindow, BOTTOM, MATCH_PARENT, mDecorThickness, 0, 0); 128 assertInsetGreaterOrEqual(mTestActivity, BOTTOM, mDecorThickness); 129 130 // Decor to the left 131 updateWindow(decorWindow, LEFT, mDecorThickness, MATCH_PARENT, 0, 0); 132 assertInsetGreaterOrEqual(mTestActivity, LEFT, mDecorThickness); 133 134 // Decor to the right 135 updateWindow(decorWindow, RIGHT, mDecorThickness, MATCH_PARENT, 0, 0); 136 assertInsetGreaterOrEqual(mTestActivity, RIGHT, mDecorThickness); 137 } 138 139 @Test 140 public void testMultipleDecors() throws Exception { 141 // Test 2 decor windows on-top. 142 createDecorWindow(TOP, MATCH_PARENT, mHalfDecorThickness); 143 assertInsetGreaterOrEqual(mTestActivity, TOP, mHalfDecorThickness); 144 createDecorWindow(TOP, MATCH_PARENT, mDecorThickness); 145 assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness); 146 147 // And one at the bottom. 148 createDecorWindow(BOTTOM, MATCH_PARENT, mHalfDecorThickness); 149 assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness); 150 assertInsetGreaterOrEqual(mTestActivity, BOTTOM, mHalfDecorThickness); 151 } 152 153 @Test 154 public void testFlagChange() throws Exception { 155 WindowInsets initialInsets = getInsets(mTestActivity); 156 157 final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness); 158 assertTopInsetEquals(mTestActivity, mDecorThickness); 159 160 updateWindow(decorWindow, TOP, MATCH_PARENT, mDecorThickness, 161 0, PRIVATE_FLAG_IS_SCREEN_DECOR); 162 163 // TODO: fix test and re-enable assertion. 164 // initialInsets was not actually immutable and just updated to the current insets, 165 // meaning this assertion never actually tested anything. Now that WindowInsets actually is 166 // immutable, it turns out the test was broken. 167 // assertTopInsetEquals(mTestActivity, initialInsets.getSystemWindowInsetTop()); 168 169 updateWindow(decorWindow, TOP, MATCH_PARENT, mDecorThickness, 170 PRIVATE_FLAG_IS_SCREEN_DECOR, PRIVATE_FLAG_IS_SCREEN_DECOR); 171 assertTopInsetEquals(mTestActivity, mDecorThickness); 172 } 173 174 @Test 175 public void testRemoval() throws Exception { 176 WindowInsets initialInsets = getInsets(mTestActivity); 177 178 final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness); 179 assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness); 180 181 removeWindow(decorWindow); 182 assertTopInsetEquals(mTestActivity, initialInsets.getSystemWindowInsetTop()); 183 } 184 185 private View createDecorWindow(int gravity, int width, int height) { 186 return createWindow("decorWindow", gravity, width, height, RED, 187 FLAG_LAYOUT_IN_SCREEN, PRIVATE_FLAG_IS_SCREEN_DECOR); 188 } 189 190 private View createWindow(String name, int gravity, int width, int height, int color, int flags, 191 int privateFlags) { 192 193 final View[] viewHolder = new View[1]; 194 final int finalFlag = flags 195 | FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_NOT_TOUCHABLE; 196 197 // Needs to run on the UI thread. 198 Handler.getMain().runWithScissors(() -> { 199 final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 200 width, height, TYPE_APPLICATION_OVERLAY, finalFlag, PixelFormat.OPAQUE); 201 lp.gravity = gravity; 202 lp.privateFlags |= privateFlags; 203 204 final TextView view = new TextView(mContext); 205 view.setText("ScreenDecorWindowTests - " + name); 206 view.setBackgroundColor(color); 207 mWm.addView(view, lp); 208 mWindows.add(view); 209 viewHolder[0] = view; 210 }, 0); 211 212 waitForIdle(); 213 return viewHolder[0]; 214 } 215 216 private void updateWindow(View v, int gravity, int width, int height, 217 int privateFlags, int privateFlagsMask) { 218 // Needs to run on the UI thread. 219 Handler.getMain().runWithScissors(() -> { 220 final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) v.getLayoutParams(); 221 lp.gravity = gravity; 222 lp.width = width; 223 lp.height = height; 224 setPrivateFlags(lp, privateFlags, privateFlagsMask); 225 226 mWm.updateViewLayout(v, lp); 227 }, 0); 228 229 waitForIdle(); 230 } 231 232 private void removeWindow(View v) { 233 Handler.getMain().runWithScissors(() -> mWm.removeView(v), 0); 234 mWindows.remove(v); 235 waitForIdle(); 236 } 237 238 private WindowInsets getInsets(Activity a) { 239 return new WindowInsets(a.getWindow().getDecorView().getRootWindowInsets()); 240 } 241 242 /** 243 * Set the flags of the window, as per the 244 * {@link WindowManager.LayoutParams WindowManager.LayoutParams} 245 * flags. 246 * 247 * @param flags The new window flags (see WindowManager.LayoutParams). 248 * @param mask Which of the window flag bits to modify. 249 */ 250 public void setPrivateFlags(WindowManager.LayoutParams lp, int flags, int mask) { 251 lp.flags = (lp.flags & ~mask) | (flags & mask); 252 } 253 254 /** 255 * Asserts the top inset of {@param activity} is equal to {@param expected} waiting as needed. 256 */ 257 private void assertTopInsetEquals(Activity activity, int expected) throws Exception { 258 waitFor(() -> getInsets(activity).getSystemWindowInsetTop() == expected); 259 assertEquals(expected, getInsets(activity).getSystemWindowInsetTop()); 260 } 261 262 /** 263 * Asserts the inset at {@param side} of {@param activity} is equal to {@param expected} 264 * waiting as needed. 265 */ 266 private void assertInsetGreaterOrEqual(Activity activity, int side, int expected) 267 throws Exception { 268 waitFor(() -> { 269 final WindowInsets insets = getInsets(activity); 270 switch (side) { 271 case TOP: return insets.getSystemWindowInsetTop() >= expected; 272 case BOTTOM: return insets.getSystemWindowInsetBottom() >= expected; 273 case LEFT: return insets.getSystemWindowInsetLeft() >= expected; 274 case RIGHT: return insets.getSystemWindowInsetRight() >= expected; 275 default: return true; 276 } 277 }); 278 279 final WindowInsets insets = getInsets(activity); 280 switch (side) { 281 case TOP: assertGreaterOrEqual(insets.getSystemWindowInsetTop(), expected); break; 282 case BOTTOM: assertGreaterOrEqual(insets.getSystemWindowInsetBottom(), expected); break; 283 case LEFT: assertGreaterOrEqual(insets.getSystemWindowInsetLeft(), expected); break; 284 case RIGHT: assertGreaterOrEqual(insets.getSystemWindowInsetRight(), expected); break; 285 } 286 } 287 288 /** Asserts that the first entry is greater than or equal to the second entry. */ 289 private void assertGreaterOrEqual(int first, int second) throws Exception { 290 Assert.assertTrue("Excepted " + first + " >= " + second, first >= second); 291 } 292 293 private void waitFor(BooleanSupplier waitCondition) { 294 int retriesLeft = 5; 295 do { 296 if (waitCondition.getAsBoolean()) { 297 break; 298 } 299 try { 300 Thread.sleep(500); 301 } catch (InterruptedException e) { 302 // Well I guess we are not waiting... 303 } 304 } while (retriesLeft-- > 0); 305 } 306 307 private void finishActivity(Activity a) { 308 if (a == null) { 309 return; 310 } 311 a.finish(); 312 waitForIdle(); 313 } 314 315 private void waitForIdle() { 316 mInstrumentation.waitForIdleSync(); 317 } 318 319 private Activity startActivityOnDisplay(Class<?> cls, int displayId) { 320 final Intent intent = new Intent(mContext, cls); 321 intent.addFlags(FLAG_ACTIVITY_NEW_TASK); 322 final ActivityOptions options = ActivityOptions.makeBasic(); 323 options.setLaunchDisplayId(displayId); 324 final Activity activity = mInstrumentation.startActivitySync(intent, options.toBundle()); 325 waitForIdle(); 326 327 assertEquals(displayId, activity.getDisplay().getDisplayId()); 328 return activity; 329 } 330 331 private Pair<VirtualDisplay, ImageReader> createDisplay() { 332 final DisplayManager dm = mContext.getSystemService(DisplayManager.class); 333 final DisplayInfo displayInfo = new DisplayInfo(); 334 final Display defaultDisplay = dm.getDisplay(DEFAULT_DISPLAY); 335 defaultDisplay.getDisplayInfo(displayInfo); 336 final String name = "ScreenDecorWindowTests"; 337 int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; 338 339 final ImageReader imageReader = ImageReader.newInstance( 340 displayInfo.logicalWidth, displayInfo.logicalHeight, PixelFormat.RGBA_8888, 2); 341 342 final VirtualDisplay display = dm.createVirtualDisplay(name, displayInfo.logicalWidth, 343 displayInfo.logicalHeight, displayInfo.logicalDensityDpi, imageReader.getSurface(), 344 flags); 345 346 return Pair.create(display, imageReader); 347 } 348 349 public static class TestActivity extends Activity { 350 } 351 } 352