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.EXTRA_LAYOUT_PARAMS;
20 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
21 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
22 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
23 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
24 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
25 
26 import static androidx.test.InstrumentationRegistry.getInstrumentation;
27 
28 import static org.hamcrest.Matchers.is;
29 
30 import android.app.Activity;
31 import android.content.Intent;
32 import android.graphics.PixelFormat;
33 import android.graphics.Point;
34 import android.os.Bundle;
35 import android.platform.test.annotations.Presubmit;
36 import android.view.Gravity;
37 import android.view.View;
38 import android.view.ViewGroup;
39 import android.view.Window;
40 import android.view.WindowManager.LayoutParams;
41 import android.widget.FrameLayout;
42 
43 import androidx.test.filters.FlakyTest;
44 import androidx.test.filters.SmallTest;
45 import androidx.test.rule.ActivityTestRule;
46 
47 import com.android.compatibility.common.util.PollingCheck;
48 
49 import org.hamcrest.Matcher;
50 import org.junit.Before;
51 import org.junit.Rule;
52 import org.junit.Test;
53 import org.junit.rules.ErrorCollector;
54 
55 import java.util.function.Supplier;
56 
57 @SmallTest
58 @Presubmit
59 public class LocationInWindowTests {
60 
61     @Rule
62     public final ErrorCollector mErrorCollector = new ErrorCollector();
63 
64     @Rule
65     public final ActivityTestRule<TestActivity> mActivity =
66             new ActivityTestRule<>(TestActivity.class, false /* initialTouchMode */,
67                     false /* launchActivity */);
68 
69     private LayoutParams mLayoutParams;
70 
71     @Before
setUp()72     public void setUp() {
73         mLayoutParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT, LayoutParams.TYPE_APPLICATION,
74                 LayoutParams.FLAG_LAYOUT_IN_SCREEN | LayoutParams.FLAG_LAYOUT_INSET_DECOR,
75                 PixelFormat.TRANSLUCENT);
76     }
77 
78     @Test
testLocationInWindow_appWindow()79     public void testLocationInWindow_appWindow() {
80         runTest(mLayoutParams);
81     }
82 
83     @Test
testLocationInWindow_appWindow_fullscreen()84     public void testLocationInWindow_appWindow_fullscreen() {
85         mLayoutParams.flags |= LayoutParams.FLAG_FULLSCREEN;
86         runTest(mLayoutParams);
87     }
88 
89     @Test
testLocationInWindow_floatingWindow()90     public void testLocationInWindow_floatingWindow() {
91         mLayoutParams.height = 100;
92         mLayoutParams.width = 100;
93         mLayoutParams.gravity = Gravity.CENTER;
94         mLayoutParams.flags &= ~(FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR);
95         runTest(mLayoutParams);
96     }
97 
98     @Test
testLocationInWindow_appWindow_displayCutoutNever()99     public void testLocationInWindow_appWindow_displayCutoutNever() {
100         mLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
101         runTest(mLayoutParams);
102     }
103 
104     @Test
testLocationInWindow_appWindow_displayCutoutShortEdges()105     public void testLocationInWindow_appWindow_displayCutoutShortEdges() {
106         mLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
107         runTest(mLayoutParams);
108     }
109 
runTest(LayoutParams lp)110     private void runTest(LayoutParams lp) {
111         final TestActivity activity = launchAndWait(mActivity, lp);
112         PollingCheck.waitFor(() -> getOnMainSync(activity::isEnterAnimationComplete));
113 
114         runOnMainSync(() -> {
115             assertThatViewGetLocationInWindowIsCorrect(activity.mView);
116         });
117     }
118 
assertThatViewGetLocationInWindowIsCorrect(View v)119     private void assertThatViewGetLocationInWindowIsCorrect(View v) {
120         final float[] expected = new float[] {0, 0};
121 
122         v.getMatrix().mapPoints(expected);
123         expected[0] += v.getLeft();
124         expected[1] += v.getTop();
125 
126         final View parent = (View)v.getParent();
127         expected[0] -= parent.getScrollX();
128         expected[1] -= parent.getScrollY();
129 
130         final Point parentLocation = locationInWindowAsPoint(parent);
131         expected[0] += parentLocation.x;
132         expected[1] += parentLocation.y;
133 
134         Point actual = locationInWindowAsPoint(v);
135 
136         assertThat("getLocationInWindow returned wrong location",
137                 actual, is(new Point((int) expected[0], (int) expected[1])));
138     }
139 
locationInWindowAsPoint(View v)140     private Point locationInWindowAsPoint(View v) {
141         final int[] out = new int[2];
142         v.getLocationInWindow(out);
143         return new Point(out[0], out[1]);
144     }
145 
assertThat(String reason, T actual, Matcher<? super T> matcher)146     private <T> void assertThat(String reason, T actual, Matcher<? super T> matcher) {
147         mErrorCollector.checkThat(reason, actual, matcher);
148     }
149 
getOnMainSync(Supplier<R> f)150     private <R> R getOnMainSync(Supplier<R> f) {
151         final Object[] result = new Object[1];
152         runOnMainSync(() -> result[0] = f.get());
153         //noinspection unchecked
154         return (R) result[0];
155     }
156 
runOnMainSync(Runnable runnable)157     private void runOnMainSync(Runnable runnable) {
158         getInstrumentation().runOnMainSync(runnable);
159     }
160 
launchAndWait(ActivityTestRule<T> rule, LayoutParams lp)161     private <T extends Activity> T launchAndWait(ActivityTestRule<T> rule,
162             LayoutParams lp) {
163         final T activity = rule.launchActivity(
164                 new Intent().putExtra(EXTRA_LAYOUT_PARAMS, lp));
165         PollingCheck.waitFor(activity::hasWindowFocus);
166         return activity;
167     }
168 
169     public static class TestActivity extends Activity {
170 
171         static final String EXTRA_LAYOUT_PARAMS = "extra.layout_params";
172         private View mView;
173         private boolean mEnterAnimationComplete;
174 
175         @Override
onCreate(Bundle savedInstanceState)176         protected void onCreate(Bundle savedInstanceState) {
177             super.onCreate(savedInstanceState);
178             getWindow().requestFeature(Window.FEATURE_NO_TITLE);
179 
180             FrameLayout frame = new FrameLayout(this);
181             frame.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
182             frame.setPadding(1, 2, 3, 4);
183             setContentView(frame);
184 
185             mView = new View(this);
186             final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(1, 1);
187             lp.leftMargin = 6;
188             lp.topMargin = 7;
189             frame.addView(mView, lp);
190             mView.setTranslationX(8);
191             mView.setTranslationY(9);
192             mView.setScrollX(10);
193             mView.setScrollY(11);
194             frame.setScrollX(12);
195             frame.setScrollY(13);
196 
197             if (getIntent() != null
198                     && getIntent().getParcelableExtra(EXTRA_LAYOUT_PARAMS) != null) {
199                 getWindow().setAttributes(getIntent().getParcelableExtra(EXTRA_LAYOUT_PARAMS));
200             }
201         }
202 
isEnterAnimationComplete()203         public boolean isEnterAnimationComplete() {
204             return mEnterAnimationComplete;
205         }
206 
207         @Override
onEnterAnimationComplete()208         public void onEnterAnimationComplete() {
209             super.onEnterAnimationComplete();
210             mEnterAnimationComplete = true;
211         }
212     }
213 }
214