1 /* 2 * Copyright (C) 2016 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 package android.view.surfacecontrol.cts; 17 18 import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER; 19 20 import android.animation.ObjectAnimator; 21 import android.animation.PropertyValuesHolder; 22 import android.animation.ValueAnimator; 23 import android.content.Context; 24 import android.graphics.Canvas; 25 import android.graphics.Color; 26 import android.graphics.Paint; 27 import android.media.MediaPlayer; 28 import android.view.Gravity; 29 import android.view.SurfaceControl; 30 import android.view.SurfaceHolder; 31 import android.view.SurfaceView; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.view.animation.LinearInterpolator; 35 import android.view.cts.surfacevalidator.AnimationFactory; 36 import android.view.cts.surfacevalidator.AnimationTestCase; 37 import android.view.cts.surfacevalidator.PixelChecker; 38 import android.view.cts.surfacevalidator.ViewFactory; 39 import android.view.cts.util.CapturedActivityWithResource; 40 import android.widget.FrameLayout; 41 42 import androidx.test.filters.LargeTest; 43 import androidx.test.rule.ActivityTestRule; 44 import androidx.test.runner.AndroidJUnit4; 45 46 import org.junit.Before; 47 import org.junit.Rule; 48 import org.junit.Test; 49 import org.junit.rules.TestName; 50 import org.junit.runner.RunWith; 51 52 import java.util.concurrent.CountDownLatch; 53 import java.util.concurrent.TimeUnit; 54 55 @RunWith(AndroidJUnit4.class) 56 @LargeTest 57 public class SurfaceViewSyncTest { 58 private static final String TAG = "SurfaceViewSyncTests"; 59 60 private static final long WAIT_TIMEOUT_S = 5L * HW_TIMEOUT_MULTIPLIER; 61 62 @Rule 63 public ActivityTestRule<CapturedActivityWithResource> mActivityRule = 64 new ActivityTestRule<>(CapturedActivityWithResource.class); 65 66 @Rule 67 public TestName mName = new TestName(); 68 69 private CapturedActivityWithResource mActivity; 70 private MediaPlayer mMediaPlayer; 71 72 @Before setup()73 public void setup() { 74 mActivity = mActivityRule.getActivity(); 75 mMediaPlayer = mActivity.getMediaPlayer(); 76 } 77 makeInfinite(ValueAnimator a)78 private static ValueAnimator makeInfinite(ValueAnimator a) { 79 a.setRepeatMode(ObjectAnimator.REVERSE); 80 a.setRepeatCount(ObjectAnimator.INFINITE); 81 a.setDuration(200); 82 a.setInterpolator(new LinearInterpolator()); 83 return a; 84 } 85 86 /////////////////////////////////////////////////////////////////////////// 87 // ViewFactories 88 /////////////////////////////////////////////////////////////////////////// 89 90 private final ViewFactory mEmptySurfaceViewFactory = context -> { 91 SurfaceView surfaceView = new SurfaceView(context); 92 93 // prevent transparent region optimization, which is invalid for a SurfaceView moving around 94 surfaceView.setWillNotDraw(false); 95 96 return surfaceView; 97 }; 98 99 private final ViewFactory mGreenSurfaceViewFactory = new ViewFactory() { 100 private final CountDownLatch mCountDownLatch = new CountDownLatch(1); 101 102 @Override 103 public View createView(Context context) { 104 SurfaceView surfaceView = new SurfaceView(context); 105 106 // prevent transparent region optimization, which is invalid for a SurfaceView moving 107 // around 108 surfaceView.setWillNotDraw(false); 109 110 surfaceView.getHolder().setFixedSize(640, 480); 111 surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { 112 @Override 113 public void surfaceCreated(SurfaceHolder holder) {} 114 115 @Override 116 public void surfaceChanged(SurfaceHolder holder, int format, int width, 117 int height) { 118 Canvas canvas = holder.lockCanvas(); 119 canvas.drawColor(Color.GREEN); 120 holder.unlockCanvasAndPost(canvas); 121 mCountDownLatch.countDown(); 122 } 123 124 @Override 125 public void surfaceDestroyed(SurfaceHolder holder) {} 126 }); 127 return surfaceView; 128 } 129 130 @Override 131 public boolean waitForReady() { 132 try { 133 return mCountDownLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS); 134 } catch (InterruptedException e) { 135 return false; 136 } 137 } 138 }; 139 140 private final ViewFactory mVideoViewFactory = new ViewFactory() { 141 private final CountDownLatch mCountDownLatch = new CountDownLatch(1); 142 143 @Override 144 public View createView(Context context) { 145 SurfaceView surfaceView = new SurfaceView(context); 146 147 // prevent transparent region optimization, which is invalid for a SurfaceView moving 148 // around 149 surfaceView.setWillNotDraw(false); 150 151 surfaceView.getHolder().setFixedSize(640, 480); 152 surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { 153 @Override 154 public void surfaceCreated(SurfaceHolder holder) { 155 // When using MediaPlayer, we need to wait until the first frame is drawn since 156 // it can be rendered asynchronously. Merge a commit callback transaction so 157 // the next draw into the SV gets the callback invoked when content is rendered 158 // on screen. This is a good signal to know when the test can start. 159 SurfaceControl.Transaction t = new SurfaceControl.Transaction(); 160 t.addTransactionCommittedListener(Runnable::run, mCountDownLatch::countDown); 161 surfaceView.applyTransactionToFrame(t); 162 163 mMediaPlayer.setSurface(holder.getSurface()); 164 mMediaPlayer.start(); 165 } 166 167 @Override 168 public void surfaceChanged(SurfaceHolder holder, int format, int width, 169 int height) { 170 } 171 172 @Override 173 public void surfaceDestroyed(SurfaceHolder holder) { 174 mMediaPlayer.pause(); 175 mMediaPlayer.setSurface(null); 176 } 177 }); 178 return surfaceView; 179 } 180 181 @Override 182 public boolean waitForReady() { 183 try { 184 return mCountDownLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS); 185 } catch (InterruptedException e) { 186 return false; 187 } 188 } 189 }; 190 191 private final PixelChecker mBlackPixelChecker = new PixelChecker() { 192 @Override 193 public boolean checkPixels(int blackishPixelCount, int width, int height) { 194 return blackishPixelCount == 0; 195 } 196 }; 197 198 /////////////////////////////////////////////////////////////////////////// 199 // AnimationFactories 200 /////////////////////////////////////////////////////////////////////////// 201 202 private final AnimationFactory mSmallScaleAnimationFactory = view -> { 203 view.setPivotX(0); 204 view.setPivotY(0); 205 PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(View.SCALE_X, 0.01f, 1f); 206 PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 0.01f, 1f); 207 return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY)); 208 }; 209 210 private final AnimationFactory mBigScaleAnimationFactory = view -> { 211 view.setTranslationX(10); 212 view.setTranslationY(10); 213 view.setPivotX(0); 214 view.setPivotY(0); 215 PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(View.SCALE_X, 1f, 3f); 216 PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1f, 3f); 217 return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY)); 218 }; 219 220 private static final AnimationFactory sFixedSizeWithViewSizeAnimationFactory = view -> { 221 ValueAnimator anim = ValueAnimator.ofInt(0, 100); 222 anim.addUpdateListener(valueAnimator -> { 223 ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); 224 layoutParams.width++; 225 if (layoutParams.height++ > 1500) { 226 layoutParams.height = 320; 227 layoutParams.width = 240; 228 } 229 view.setLayoutParams(layoutParams); 230 231 if ((Integer) valueAnimator.getAnimatedValue() % 3 == 0) { 232 ((SurfaceView) view).getHolder().setFixedSize(320, 240); 233 } else if ((Integer) valueAnimator.getAnimatedValue() % 7 == 0) { 234 ((SurfaceView) view).getHolder().setFixedSize(1280, 960); 235 } 236 }); 237 return makeInfinite(anim); 238 }; 239 240 private static final AnimationFactory sFixedSizeAnimationFactory = view -> { 241 ValueAnimator anim = ValueAnimator.ofInt(0, 100); 242 anim.addUpdateListener(valueAnimator -> { 243 if ((Integer) valueAnimator.getAnimatedValue() % 2 == 0) { 244 ((SurfaceView) view).getHolder().setFixedSize(320, 240); 245 } else { 246 ((SurfaceView) view).getHolder().setFixedSize(1280, 960); 247 } 248 }); 249 return makeInfinite(anim); 250 }; 251 252 private final AnimationFactory mTranslateAnimationFactory = view -> { 253 PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 10f, 30f); 254 PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 10f, 30f); 255 return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY)); 256 }; 257 258 /////////////////////////////////////////////////////////////////////////// 259 // Tests 260 /////////////////////////////////////////////////////////////////////////// 261 262 /** Draws a moving 10x10 black rectangle, validates 100 pixels of black are seen each frame */ 263 @Test testSmallRect()264 public void testSmallRect() throws Throwable { 265 mActivity.verifyTest(new AnimationTestCase(context -> new View(context) { 266 // draw a single pixel 267 final Paint mBlackPaint = new Paint(); 268 269 @Override 270 protected void onDraw(Canvas canvas) { 271 canvas.drawRect(0, 0, 10, 10, mBlackPaint); 272 } 273 274 @SuppressWarnings("unused") 275 void setOffset(int offset) { 276 // Note: offset by integer values, to ensure no rounding 277 // is done in rendering layer, as that may be brittle 278 setTranslationX(offset); 279 setTranslationY(offset); 280 } 281 }, new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP), 282 view -> makeInfinite(ObjectAnimator.ofInt(view, "offset", 10, 30)), 283 new PixelChecker() { 284 @Override 285 public boolean checkPixels(int blackishPixelCount, int width, int height) { 286 return blackishPixelCount >= 90 && blackishPixelCount <= 110; 287 } 288 }), mName); 289 } 290 291 /** 292 * Verifies that a SurfaceView without a surface is entirely black, with pixel count being 293 * approximate to avoid rounding brittleness. 294 */ 295 @Test testEmptySurfaceView()296 public void testEmptySurfaceView() throws Throwable { 297 mActivity.verifyTest(new AnimationTestCase( 298 mEmptySurfaceViewFactory, 299 new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP), 300 mTranslateAnimationFactory, 301 new PixelChecker() { 302 @Override 303 public boolean checkPixels(int blackishPixelCount, int width, int height) { 304 return blackishPixelCount > 9000 && blackishPixelCount < 11000; 305 } 306 }), mName); 307 } 308 309 @Test testSurfaceViewSmallScale()310 public void testSurfaceViewSmallScale() throws Throwable { 311 mActivity.verifyTest(new AnimationTestCase( 312 mGreenSurfaceViewFactory, 313 new FrameLayout.LayoutParams(320, 240, Gravity.LEFT | Gravity.TOP), 314 mSmallScaleAnimationFactory, 315 new PixelChecker() { 316 @Override 317 public boolean checkPixels(int blackishPixelCount, int width, int height) { 318 return blackishPixelCount == 0; 319 } 320 }), mName); 321 } 322 323 @Test testSurfaceViewBigScale()324 public void testSurfaceViewBigScale() throws Throwable { 325 mActivity.verifyTest(new AnimationTestCase( 326 mGreenSurfaceViewFactory, 327 new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP), 328 mBigScaleAnimationFactory, mBlackPixelChecker), mName); 329 } 330 331 332 /** 333 * Change requested surface size and SurfaceView size and verify buffers always fill to 334 * SurfaceView size. b/190449942 335 */ 336 @Test testSurfaceViewFixedSizeWithViewSizeChanges()337 public void testSurfaceViewFixedSizeWithViewSizeChanges() throws Throwable { 338 mActivity.verifyTest(new AnimationTestCase( 339 mVideoViewFactory, 340 new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP), 341 sFixedSizeWithViewSizeAnimationFactory, mBlackPixelChecker), mName); 342 } 343 344 /** 345 * Change requested surface size and verify buffers always fill to SurfaceView size. 346 * b/194458377 347 */ 348 @Test testSurfaceViewFixedSizeChanges()349 public void testSurfaceViewFixedSizeChanges() throws Throwable { 350 mActivity.verifyTest(new AnimationTestCase( 351 mVideoViewFactory, 352 new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP), 353 sFixedSizeAnimationFactory, mBlackPixelChecker), mName); 354 } 355 356 @Test testVideoSurfaceViewTranslate()357 public void testVideoSurfaceViewTranslate() throws Throwable { 358 mActivity.verifyTest(new AnimationTestCase( 359 mVideoViewFactory, 360 new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP), 361 mTranslateAnimationFactory, mBlackPixelChecker), mName); 362 } 363 364 @Test testVideoSurfaceViewRotated()365 public void testVideoSurfaceViewRotated() throws Throwable { 366 mActivity.verifyTest(new AnimationTestCase( 367 mVideoViewFactory, 368 new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP), 369 view -> makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view, 370 PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 10f, 30f), 371 PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 10f, 30f), 372 PropertyValuesHolder.ofFloat(View.ROTATION, 45f, 45f))), 373 mBlackPixelChecker), mName); 374 } 375 376 @Test testVideoSurfaceViewEdgeCoverage()377 public void testVideoSurfaceViewEdgeCoverage() throws Throwable { 378 mActivity.verifyTest(new AnimationTestCase( 379 mVideoViewFactory, 380 new FrameLayout.LayoutParams(640, 480, Gravity.CENTER), 381 view -> { 382 ViewGroup parent = (ViewGroup) view.getParent(); 383 final int x = parent.getWidth() / 2; 384 final int y = parent.getHeight() / 2; 385 386 // Animate from left, to top, to right, to bottom 387 return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view, 388 PropertyValuesHolder.ofFloat(View.TRANSLATION_X, -x, 0, x, 0, -x), 389 PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 0, -y, 0, y, 0))); 390 }, mBlackPixelChecker), mName); 391 } 392 393 @Test testVideoSurfaceViewCornerCoverage()394 public void testVideoSurfaceViewCornerCoverage() throws Throwable { 395 mActivity.verifyTest(new AnimationTestCase( 396 mVideoViewFactory, 397 new FrameLayout.LayoutParams(640, 480, Gravity.CENTER), 398 view -> { 399 ViewGroup parent = (ViewGroup) view.getParent(); 400 final int x = parent.getWidth() / 2; 401 final int y = parent.getHeight() / 2; 402 403 // Animate from top left, to top right, to bottom right, to bottom left 404 return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view, 405 PropertyValuesHolder.ofFloat(View.TRANSLATION_X, -x, x, x, -x, -x), 406 PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, -y, -y, y, y, -y))); 407 }, mBlackPixelChecker), mName); 408 } 409 } 410