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