Home | History | Annotate | Download | only in wm
      1 /*
      2  * Copyright (C) 2018 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.server.wm.LocationOnScreenTests.TestActivity.COLOR_TOLERANCE;
     20 import static android.server.wm.LocationOnScreenTests.TestActivity.EXTRA_LAYOUT_PARAMS;
     21 import static android.server.wm.LocationOnScreenTests.TestActivity.TEST_COLOR_1;
     22 import static android.server.wm.LocationOnScreenTests.TestActivity.TEST_COLOR_2;
     23 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
     24 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
     25 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
     26 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
     27 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
     28 
     29 import static androidx.test.InstrumentationRegistry.getInstrumentation;
     30 
     31 import static org.hamcrest.Matchers.is;
     32 
     33 import android.app.Activity;
     34 import android.content.Context;
     35 import android.content.Intent;
     36 import android.graphics.Bitmap;
     37 import android.graphics.Canvas;
     38 import android.graphics.Color;
     39 import android.graphics.Paint;
     40 import android.graphics.PixelFormat;
     41 import android.graphics.Point;
     42 import android.os.Bundle;
     43 import android.platform.test.annotations.Presubmit;
     44 import android.view.Gravity;
     45 import android.view.View;
     46 import android.view.ViewGroup;
     47 import android.view.Window;
     48 import android.view.WindowManager.LayoutParams;
     49 import android.widget.FrameLayout;
     50 
     51 import androidx.test.filters.FlakyTest;
     52 import androidx.test.filters.SmallTest;
     53 import androidx.test.rule.ActivityTestRule;
     54 
     55 import com.android.compatibility.common.util.BitmapUtils;
     56 import com.android.compatibility.common.util.PollingCheck;
     57 
     58 import org.hamcrest.Matcher;
     59 import org.junit.Assert;
     60 import org.junit.Before;
     61 import org.junit.Rule;
     62 import org.junit.Test;
     63 import org.junit.rules.ErrorCollector;
     64 
     65 import java.util.function.Supplier;
     66 
     67 @FlakyTest(detail = "until proven non-flaky")
     68 @SmallTest
     69 @Presubmit
     70 public class LocationOnScreenTests {
     71 
     72     @Rule
     73     public final ErrorCollector mErrorCollector = new ErrorCollector();
     74 
     75     @Rule
     76     public final ActivityTestRule<TestActivity> mDisplayCutoutActivity =
     77             new ActivityTestRule<>(TestActivity.class, false /* initialTouchMode */,
     78                     false /* launchActivity */);
     79 
     80     private LayoutParams mLayoutParams;
     81     private Context mContext;
     82 
     83     @Before
     84     public void setUp() {
     85         mContext = getInstrumentation().getContext();
     86         mLayoutParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT, LayoutParams.TYPE_APPLICATION,
     87                 LayoutParams.FLAG_LAYOUT_IN_SCREEN | LayoutParams.FLAG_LAYOUT_INSET_DECOR,
     88                 PixelFormat.TRANSLUCENT);
     89     }
     90 
     91     @Test
     92     public void testLocationOnDisplay_appWindow() {
     93         runTest(mLayoutParams);
     94     }
     95 
     96     @Test
     97     public void testLocationOnDisplay_appWindow_fullscreen() {
     98         mLayoutParams.flags |= LayoutParams.FLAG_FULLSCREEN;
     99         runTest(mLayoutParams);
    100     }
    101 
    102     @Test
    103     public void testLocationOnDisplay_floatingWindow() {
    104         mLayoutParams.height = 50;
    105         mLayoutParams.width = 50;
    106         mLayoutParams.gravity = Gravity.CENTER;
    107         mLayoutParams.flags &= ~(FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR);
    108         runTest(mLayoutParams);
    109     }
    110 
    111     @Test
    112     public void testLocationOnDisplay_appWindow_displayCutoutNever() {
    113         mLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
    114         runTest(mLayoutParams);
    115     }
    116 
    117     @Test
    118     public void testLocationOnDisplay_appWindow_displayCutoutShortEdges() {
    119         mLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
    120         runTest(mLayoutParams);
    121     }
    122 
    123     private void runTest(LayoutParams lp) {
    124         final TestActivity activity = launchAndWait(mDisplayCutoutActivity, lp);
    125         PollingCheck.waitFor(() -> getOnMainSync(activity::isEnterAnimationComplete));
    126 
    127         Point actual = getOnMainSync(activity::getViewLocationOnScreen);
    128         Point expected = findTestColorsInScreenshot(actual);
    129 
    130         assertThat("View.locationOnScreen returned incorrect value", actual, is(expected));
    131     }
    132 
    133     private <T> void assertThat(String reason, T actual, Matcher<? super T> matcher) {
    134         mErrorCollector.checkThat(reason, actual, matcher);
    135     }
    136 
    137     private <R> R getOnMainSync(Supplier<R> f) {
    138         final Object[] result = new Object[1];
    139         runOnMainSync(() -> result[0] = f.get());
    140         //noinspection unchecked
    141         return (R) result[0];
    142     }
    143 
    144     private void runOnMainSync(Runnable runnable) {
    145         getInstrumentation().runOnMainSync(runnable);
    146     }
    147 
    148     private <T extends Activity> T launchAndWait(ActivityTestRule<T> rule,
    149             LayoutParams lp) {
    150         final T activity = rule.launchActivity(
    151                 new Intent().putExtra(EXTRA_LAYOUT_PARAMS, lp));
    152         PollingCheck.waitFor(activity::hasWindowFocus);
    153         return activity;
    154     }
    155 
    156     private Point findTestColorsInScreenshot(Point guess) {
    157         final Bitmap screenshot = getInstrumentation().getUiAutomation().takeScreenshot();
    158 
    159         // We have a good guess from locationOnScreen - check there first to avoid having to go over
    160         // the entire bitmap. Also increases robustness in the extremely unlikely case that those
    161         // colors are visible elsewhere.
    162         if (isTestColors(screenshot, guess.x, guess.y)) {
    163             return guess;
    164         }
    165 
    166         for (int y = 0; y < screenshot.getHeight(); y++) {
    167             for (int x = 0; x < screenshot.getWidth() - 1; x++) {
    168                 if (isTestColors(screenshot, x, y)) {
    169                     return new Point(x, y);
    170                 }
    171             }
    172         }
    173         String path = mContext.getExternalFilesDir(null).getPath();
    174         String file = "location_on_screen_failure.png";
    175         BitmapUtils.saveBitmap(screenshot, path, file);
    176         Assert.fail("No match found for TEST_COLOR_1 and TEST_COLOR_2 pixels. Check "
    177                 + path + "/" + file);
    178         return null;
    179     }
    180 
    181     private boolean isTestColors(Bitmap screenshot, int x, int y) {
    182         return sameColorWithinTolerance(screenshot.getPixel(x, y), TEST_COLOR_1)
    183                 && sameColorWithinTolerance(screenshot.getPixel(x + 1, y), TEST_COLOR_2);
    184     }
    185 
    186     /**
    187      * Returns whether two colors are considered the same.
    188      *
    189      * Some tolerance is allowed to compensate for errors introduced when screenshots are scaled.
    190      */
    191     private static boolean sameColorWithinTolerance(int pixelColor, int testColor) {
    192         final Color pColor = Color.valueOf(pixelColor);
    193         final Color tColor = Color.valueOf(testColor);
    194         return pColor.alpha() == tColor.alpha()
    195                 && Math.abs(pColor.red() - tColor.red()) <= COLOR_TOLERANCE
    196                 && Math.abs(pColor.blue() - tColor.blue()) <= COLOR_TOLERANCE
    197                 && Math.abs(pColor.green() - tColor.green()) <= COLOR_TOLERANCE;
    198     }
    199 
    200     public static class TestActivity extends Activity {
    201 
    202         static final int TEST_COLOR_1 = 0xff123456;
    203         static final int TEST_COLOR_2 = 0xfffedcba;
    204         static final int COLOR_TOLERANCE = 4;
    205         static final String EXTRA_LAYOUT_PARAMS = "extra.layout_params";
    206         private View mView;
    207         private boolean mEnterAnimationComplete;
    208 
    209         @Override
    210         protected void onCreate(Bundle savedInstanceState) {
    211             super.onCreate(savedInstanceState);
    212             getWindow().requestFeature(Window.FEATURE_NO_TITLE);
    213 
    214             FrameLayout frame = new FrameLayout(this);
    215             frame.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    216             setContentView(frame);
    217 
    218             mView = new TestView(this);
    219             frame.addView(mView, new FrameLayout.LayoutParams(2, 1, Gravity.CENTER));
    220 
    221             if (getIntent() != null
    222                     && getIntent().getParcelableExtra(EXTRA_LAYOUT_PARAMS) != null) {
    223                 getWindow().setAttributes(getIntent().getParcelableExtra(EXTRA_LAYOUT_PARAMS));
    224             }
    225         }
    226 
    227         public Point getViewLocationOnScreen() {
    228             final int[] location = new int[2];
    229             mView.getLocationOnScreen(location);
    230             return new Point(location[0], location[1]);
    231         }
    232 
    233         public boolean isEnterAnimationComplete() {
    234             return mEnterAnimationComplete;
    235         }
    236 
    237         @Override
    238         public void onEnterAnimationComplete() {
    239             super.onEnterAnimationComplete();
    240             mEnterAnimationComplete = true;
    241         }
    242     }
    243 
    244     private static class TestView extends View {
    245         private Paint mPaint = new Paint();
    246 
    247         public TestView(Context context) {
    248             super(context);
    249         }
    250 
    251         @Override
    252         protected void onDraw(Canvas canvas) {
    253             super.onDraw(canvas);
    254 
    255             mPaint.setColor(TEST_COLOR_1);
    256             canvas.drawRect(0, 0, 1, 1, mPaint);
    257             mPaint.setColor(TEST_COLOR_2);
    258             canvas.drawRect(1, 0, 2, 1, mPaint);
    259         }
    260     }
    261 }
    262