Home | History | Annotate | Download | only in wm
      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