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