1 /*
2  * Copyright (C) 2019 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 org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertNull;
23 import static org.junit.Assert.assertTrue;
24 import static org.mockito.ArgumentMatchers.anyString;
25 import static org.mockito.Mockito.clearInvocations;
26 import static org.mockito.Mockito.doAnswer;
27 import static org.mockito.Mockito.mock;
28 import static org.mockito.Mockito.spy;
29 import static org.mockito.Mockito.verify;
30 import static org.mockito.Mockito.when;
31 
32 import android.graphics.Color;
33 import android.graphics.Point;
34 import android.graphics.Rect;
35 import android.platform.test.annotations.Presubmit;
36 import android.view.SurfaceControl;
37 
38 import androidx.test.filters.SmallTest;
39 
40 import com.android.server.testutils.StubTransaction;
41 
42 import org.junit.Before;
43 import org.junit.Test;
44 import org.mockito.invocation.InvocationOnMock;
45 
46 import java.util.function.Supplier;
47 
48 @SmallTest
49 @Presubmit
50 public class LetterboxTest {
51 
52     Letterbox mLetterbox;
53     SurfaceControlMocker mSurfaces;
54     SurfaceControl.Transaction mTransaction;
55 
56     private boolean mAreCornersRounded = false;
57     private int mColor = Color.BLACK;
58     private boolean mHasWallpaperBackground = false;
59     private int mBlurRadius = 0;
60     private float mDarkScrimAlpha = 0.5f;
61     private SurfaceControl mParentSurface = mock(SurfaceControl.class);
62 
63     @Before
setUp()64     public void setUp() throws Exception {
65         mSurfaces = new SurfaceControlMocker();
66         mLetterbox = new Letterbox(mSurfaces, StubTransaction::new,
67                 () -> mAreCornersRounded, () -> Color.valueOf(mColor),
68                 () -> mHasWallpaperBackground, () -> mBlurRadius, () -> mDarkScrimAlpha,
69                 /* doubleTapCallbackX= */ x -> {}, /* doubleTapCallbackY= */ y -> {},
70                 () -> mParentSurface);
71         mTransaction = spy(StubTransaction.class);
72     }
73 
74     private static final int TOP_BAR = 0x1;
75     private static final int BOTTOM_BAR = 0x2;
76     private static final int LEFT_BAR = 0x4;
77     private static final int RIGHT_BAR = 0x8;
78 
79     @Test
testNotIntersectsOrFullyContains_usesGlobalCoordinates()80     public void testNotIntersectsOrFullyContains_usesGlobalCoordinates() {
81         final Rect outer = new Rect(0, 0, 10, 50);
82         final Point surfaceOrig = new Point(1000, 2000);
83 
84         final Rect topBar = new Rect(0, 0, 10, 2);
85         final Rect bottomBar = new Rect(0, 45, 10, 50);
86         final Rect leftBar = new Rect(0, 0, 2, 50);
87         final Rect rightBar = new Rect(8, 0, 10, 50);
88 
89         final LetterboxLayoutVerifier verifier =
90                 new LetterboxLayoutVerifier(outer, surfaceOrig, mLetterbox);
91         verifier.setBarRect(topBar, bottomBar, leftBar, rightBar);
92 
93         // top
94         verifier.setInner(0, 2, 10, 50).verifyPositions(TOP_BAR | BOTTOM_BAR, BOTTOM_BAR);
95         // bottom
96         verifier.setInner(0, 0, 10, 45).verifyPositions(TOP_BAR | BOTTOM_BAR, TOP_BAR);
97         // left
98         verifier.setInner(2, 0, 10, 50).verifyPositions(LEFT_BAR | RIGHT_BAR, RIGHT_BAR);
99         // right
100         verifier.setInner(0, 0, 8, 50).verifyPositions(LEFT_BAR | RIGHT_BAR, LEFT_BAR);
101         // top + bottom
102         verifier.setInner(0, 2, 10, 45).verifyPositions(TOP_BAR | BOTTOM_BAR, 0);
103         // left + right
104         verifier.setInner(2, 0, 8, 50).verifyPositions(LEFT_BAR | RIGHT_BAR, 0);
105         // top + left
106         verifier.setInner(2, 2, 10, 50).verifyPositions(TOP_BAR | LEFT_BAR, 0);
107         // top + left + right
108         verifier.setInner(2, 2, 8, 50).verifyPositions(TOP_BAR | LEFT_BAR | RIGHT_BAR, 0);
109         // left + right + bottom
110         verifier.setInner(2, 0, 8, 45).verifyPositions(LEFT_BAR | RIGHT_BAR | BOTTOM_BAR, 0);
111         // all
112         verifier.setInner(2, 2, 8, 45)
113                 .verifyPositions(TOP_BAR | BOTTOM_BAR | LEFT_BAR | RIGHT_BAR, 0);
114     }
115 
116     private static class LetterboxLayoutVerifier {
117         final Rect mOuter;
118         final Rect mInner = new Rect();
119         final Point mSurfaceOrig;
120         final Letterbox mLetterbox;
121         final Rect mTempRect = new Rect();
122 
123         final Rect mTop = new Rect();
124         final Rect mBottom = new Rect();
125         final Rect mLeft = new Rect();
126         final Rect mRight = new Rect();
127 
LetterboxLayoutVerifier(Rect outer, Point surfaceOrig, Letterbox letterbox)128         LetterboxLayoutVerifier(Rect outer, Point surfaceOrig, Letterbox letterbox) {
129             mOuter = new Rect(outer);
130             mSurfaceOrig = new Point(surfaceOrig);
131             mLetterbox = letterbox;
132         }
133 
setInner(int left, int top, int right, int bottom)134         LetterboxLayoutVerifier setInner(int left, int top, int right, int bottom) {
135             mInner.set(left, top, right, bottom);
136             mLetterbox.layout(mOuter, mInner, mSurfaceOrig);
137             return this;
138         }
139 
setBarRect(Rect top, Rect bottom, Rect left, Rect right)140         void setBarRect(Rect top, Rect bottom, Rect left, Rect right) {
141             mTop.set(top);
142             mBottom.set(bottom);
143             mLeft.set(left);
144             mRight.set(right);
145         }
146 
verifyPositions(int allowedPos, int noOverlapPos)147         void verifyPositions(int allowedPos, int noOverlapPos) {
148             assertEquals(mLetterbox.notIntersectsOrFullyContains(mTop),
149                     (allowedPos & TOP_BAR) != 0);
150             assertEquals(mLetterbox.notIntersectsOrFullyContains(mBottom),
151                     (allowedPos & BOTTOM_BAR) != 0);
152             assertEquals(mLetterbox.notIntersectsOrFullyContains(mLeft),
153                     (allowedPos & LEFT_BAR) != 0);
154             assertEquals(mLetterbox.notIntersectsOrFullyContains(mRight),
155                     (allowedPos & RIGHT_BAR) != 0);
156 
157             mTempRect.set(mTop.left, mTop.top, mTop.right, mTop.bottom + 1);
158             assertEquals(mLetterbox.notIntersectsOrFullyContains(mTempRect),
159                     (noOverlapPos & TOP_BAR) != 0);
160             mTempRect.set(mLeft.left, mLeft.top, mLeft.right + 1, mLeft.bottom);
161             assertEquals(mLetterbox.notIntersectsOrFullyContains(mTempRect),
162                     (noOverlapPos & LEFT_BAR) != 0);
163             mTempRect.set(mRight.left - 1, mRight.top, mRight.right, mRight.bottom);
164             assertEquals(mLetterbox.notIntersectsOrFullyContains(mTempRect),
165                     (noOverlapPos & RIGHT_BAR) != 0);
166             mTempRect.set(mBottom.left, mBottom.top - 1, mBottom.right, mBottom.bottom);
167             assertEquals(mLetterbox.notIntersectsOrFullyContains(mTempRect),
168                     (noOverlapPos & BOTTOM_BAR) != 0);
169         }
170     }
171 
172     @Test
testSurfaceOrigin_applied()173     public void testSurfaceOrigin_applied() {
174         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
175         applySurfaceChanges();
176         verify(mTransaction).setPosition(mSurfaces.top, -1000, -2000);
177     }
178 
179     @Test
testApplySurfaceChanges_setColor()180     public void testApplySurfaceChanges_setColor() {
181         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
182         applySurfaceChanges();
183 
184         verify(mTransaction).setColor(mSurfaces.top, new float[]{0, 0, 0});
185 
186         mColor = Color.GREEN;
187 
188         assertTrue(mLetterbox.needsApplySurfaceChanges());
189 
190         applySurfaceChanges();
191 
192         verify(mTransaction).setColor(mSurfaces.top, new float[]{0, 1, 0});
193     }
194 
195     @Test
testNeedsApplySurfaceChanges_wallpaperBackgroundRequested()196     public void testNeedsApplySurfaceChanges_wallpaperBackgroundRequested() {
197         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
198         applySurfaceChanges();
199 
200         verify(mTransaction).setAlpha(mSurfaces.top, 1.0f);
201         assertFalse(mLetterbox.needsApplySurfaceChanges());
202 
203         mHasWallpaperBackground = true;
204 
205         assertTrue(mLetterbox.needsApplySurfaceChanges());
206 
207         applySurfaceChanges();
208         verify(mTransaction).setAlpha(mSurfaces.fullWindowSurface, mDarkScrimAlpha);
209     }
210 
211     @Test
testNeedsApplySurfaceChanges_setParentSurface()212     public void testNeedsApplySurfaceChanges_setParentSurface() {
213         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
214         applySurfaceChanges();
215 
216         verify(mTransaction).reparent(mSurfaces.top, mParentSurface);
217         assertFalse(mLetterbox.needsApplySurfaceChanges());
218 
219         mParentSurface = mock(SurfaceControl.class);
220 
221         assertTrue(mLetterbox.needsApplySurfaceChanges());
222 
223         applySurfaceChanges();
224         verify(mTransaction).reparent(mSurfaces.top, mParentSurface);
225     }
226 
227     @Test
testApplySurfaceChanges_cornersNotRounded_surfaceFullWindowSurfaceNotCreated()228     public void testApplySurfaceChanges_cornersNotRounded_surfaceFullWindowSurfaceNotCreated() {
229         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
230         applySurfaceChanges();
231 
232         assertNull(mSurfaces.fullWindowSurface);
233     }
234 
235     @Test
testApplySurfaceChanges_cornersRounded_surfaceFullWindowSurfaceCreated()236     public void testApplySurfaceChanges_cornersRounded_surfaceFullWindowSurfaceCreated() {
237         mAreCornersRounded = true;
238         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
239         applySurfaceChanges();
240 
241         assertNotNull(mSurfaces.fullWindowSurface);
242     }
243 
244     @Test
testApplySurfaceChanges_wallpaperBackground_surfaceFullWindowSurfaceCreated()245     public void testApplySurfaceChanges_wallpaperBackground_surfaceFullWindowSurfaceCreated() {
246         mHasWallpaperBackground = true;
247         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
248         applySurfaceChanges();
249 
250         assertNotNull(mSurfaces.fullWindowSurface);
251     }
252 
253     @Test
testNotIntersectsOrFullyContains_cornersRounded()254     public void testNotIntersectsOrFullyContains_cornersRounded() {
255         mAreCornersRounded = true;
256         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(0, 0));
257         applySurfaceChanges();
258 
259         assertTrue(mLetterbox.notIntersectsOrFullyContains(new Rect(1, 2, 9, 9)));
260     }
261 
262     @Test
testSurfaceOrigin_changeCausesReapply()263     public void testSurfaceOrigin_changeCausesReapply() {
264         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
265         applySurfaceChanges();
266         clearInvocations(mTransaction);
267         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(0, 0));
268         assertTrue(mLetterbox.needsApplySurfaceChanges());
269         applySurfaceChanges();
270         verify(mTransaction).setPosition(mSurfaces.top, 0, 0);
271     }
272 
applySurfaceChanges()273     private void applySurfaceChanges() {
274         mLetterbox.applySurfaceChanges(/* syncTransaction */ mTransaction,
275                 /* pendingTransaction */ mTransaction);
276     }
277 
278     class SurfaceControlMocker implements Supplier<SurfaceControl.Builder> {
279         private SurfaceControl.Builder mLeftBuilder;
280         public SurfaceControl left;
281         private SurfaceControl.Builder mTopBuilder;
282         public SurfaceControl top;
283         private SurfaceControl.Builder mRightBuilder;
284         public SurfaceControl right;
285         private SurfaceControl.Builder mBottomBuilder;
286         public SurfaceControl bottom;
287         private SurfaceControl.Builder mFullWindowSurfaceBuilder;
288         public SurfaceControl fullWindowSurface;
289 
290         @Override
get()291         public SurfaceControl.Builder get() {
292             final SurfaceControl.Builder builder = mock(SurfaceControl.Builder.class,
293                     InvocationOnMock::getMock);
294             when(builder.setName(anyString())).then((i) -> {
295                 if (((String) i.getArgument(0)).contains("left")) {
296                     mLeftBuilder = (SurfaceControl.Builder) i.getMock();
297                 } else if (((String) i.getArgument(0)).contains("top")) {
298                     mTopBuilder = (SurfaceControl.Builder) i.getMock();
299                 } else if (((String) i.getArgument(0)).contains("right")) {
300                     mRightBuilder = (SurfaceControl.Builder) i.getMock();
301                 } else if (((String) i.getArgument(0)).contains("bottom")) {
302                     mBottomBuilder = (SurfaceControl.Builder) i.getMock();
303                 } else if (((String) i.getArgument(0)).contains("fullWindow")) {
304                     mFullWindowSurfaceBuilder = (SurfaceControl.Builder) i.getMock();
305                 }
306                 return i.getMock();
307             });
308 
309             doAnswer((i) -> {
310                 final SurfaceControl control = mock(SurfaceControl.class);
311                 if (i.getMock() == mLeftBuilder) {
312                     left = control;
313                 } else if (i.getMock() == mTopBuilder) {
314                     top = control;
315                 } else if (i.getMock() == mRightBuilder) {
316                     right = control;
317                 } else if (i.getMock() == mBottomBuilder) {
318                     bottom = control;
319                 } else if (i.getMock() == mFullWindowSurfaceBuilder) {
320                     fullWindowSurface = control;
321                 }
322                 return control;
323             }).when(builder).build();
324             return builder;
325         }
326     }
327 }
328