• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
setUp()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
testLocationOnDisplay_appWindow()92     public void testLocationOnDisplay_appWindow() {
93         runTest(mLayoutParams);
94     }
95 
96     @Test
testLocationOnDisplay_appWindow_fullscreen()97     public void testLocationOnDisplay_appWindow_fullscreen() {
98         mLayoutParams.flags |= LayoutParams.FLAG_FULLSCREEN;
99         runTest(mLayoutParams);
100     }
101 
102     @Test
testLocationOnDisplay_floatingWindow()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
testLocationOnDisplay_appWindow_displayCutoutNever()112     public void testLocationOnDisplay_appWindow_displayCutoutNever() {
113         mLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
114         runTest(mLayoutParams);
115     }
116 
117     @Test
testLocationOnDisplay_appWindow_displayCutoutShortEdges()118     public void testLocationOnDisplay_appWindow_displayCutoutShortEdges() {
119         mLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
120         runTest(mLayoutParams);
121     }
122 
runTest(LayoutParams lp)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 
assertThat(String reason, T actual, Matcher<? super T> matcher)133     private <T> void assertThat(String reason, T actual, Matcher<? super T> matcher) {
134         mErrorCollector.checkThat(reason, actual, matcher);
135     }
136 
getOnMainSync(Supplier<R> f)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 
runOnMainSync(Runnable runnable)144     private void runOnMainSync(Runnable runnable) {
145         getInstrumentation().runOnMainSync(runnable);
146     }
147 
launchAndWait(ActivityTestRule<T> rule, LayoutParams lp)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 
findTestColorsInScreenshot(Point guess)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 
isTestColors(Bitmap screenshot, int x, int y)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      */
sameColorWithinTolerance(int pixelColor, int testColor)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
onCreate(Bundle savedInstanceState)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 
getViewLocationOnScreen()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 
isEnterAnimationComplete()233         public boolean isEnterAnimationComplete() {
234             return mEnterAnimationComplete;
235         }
236 
237         @Override
onEnterAnimationComplete()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 
TestView(Context context)247         public TestView(Context context) {
248             super(context);
249         }
250 
251         @Override
onDraw(Canvas canvas)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