1 /*
2  * Copyright (C) 2015 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.uirendering.cts.testclasses;
18 
19 import static org.junit.Assert.assertEquals;
20 
21 import android.animation.ValueAnimator;
22 import android.content.pm.PackageManager;
23 import android.graphics.Color;
24 import android.graphics.ColorMatrix;
25 import android.graphics.ColorMatrixColorFilter;
26 import android.graphics.Matrix;
27 import android.graphics.Paint;
28 import android.graphics.Point;
29 import android.graphics.PorterDuff;
30 import android.graphics.PorterDuffXfermode;
31 import android.graphics.Rect;
32 import android.uirendering.cts.R;
33 import android.uirendering.cts.bitmapcomparers.MSSIMComparer;
34 import android.uirendering.cts.bitmapverifiers.ColorCountVerifier;
35 import android.uirendering.cts.bitmapverifiers.ColorVerifier;
36 import android.uirendering.cts.bitmapverifiers.RectVerifier;
37 import android.uirendering.cts.bitmapverifiers.SamplePointVerifier;
38 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
39 import android.uirendering.cts.testinfrastructure.ViewInitializer;
40 import android.uirendering.cts.util.WebViewReadyHelper;
41 import android.view.Gravity;
42 import android.view.View;
43 import android.view.ViewTreeObserver;
44 import android.webkit.WebView;
45 import android.widget.FrameLayout;
46 
47 import androidx.annotation.ColorInt;
48 import androidx.test.filters.LargeTest;
49 import androidx.test.filters.MediumTest;
50 import androidx.test.runner.AndroidJUnit4;
51 
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 
55 import java.util.concurrent.CountDownLatch;
56 
57 @MediumTest
58 @RunWith(AndroidJUnit4.class)
59 public class LayerTests extends ActivityTestBase {
60     @Test
testLayerPaintAlpha()61     public void testLayerPaintAlpha() {
62         // red channel full strength, other channels 75% strength
63         // (since 25% alpha red subtracts from them)
64         @ColorInt
65         final int expectedColor = Color.rgb(255, 191, 191);
66         createTest()
67                 .addLayout(R.layout.simple_red_layout, (ViewInitializer) view -> {
68                     // reduce alpha by 50%
69                     Paint paint = new Paint();
70                     paint.setAlpha(128);
71                     view.setLayerType(View.LAYER_TYPE_HARDWARE, paint);
72 
73                     // reduce alpha by another 50% (ensuring two alphas combine correctly)
74                     view.setAlpha(0.5f);
75                 })
76                 .runWithVerifier(new ColorVerifier(expectedColor));
77     }
78 
79     @Test
testLayerPaintSimpleAlphaWithHardware()80     public void testLayerPaintSimpleAlphaWithHardware() {
81         @ColorInt
82         final int expectedColor = Color.rgb(255, 128, 128);
83         createTest()
84                 .addLayout(R.layout.simple_red_layout, (ViewInitializer) view -> {
85                     view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
86 
87                     // reduce alpha, so that overdraw will result in a different color
88                     view.setAlpha(0.5f);
89                 })
90                 .runWithVerifier(new ColorVerifier(expectedColor));
91     }
92 
93     @Test
testLayerPaintSimpleAlphaWithSoftware()94     public void testLayerPaintSimpleAlphaWithSoftware() {
95         @ColorInt
96         final int expectedColor = Color.rgb(255, 128, 128);
97         createTest()
98                 .addLayout(R.layout.simple_red_layout, (ViewInitializer) view -> {
99                     view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
100 
101                     // reduce alpha, so that overdraw will result in a different color
102                     view.setAlpha(0.5f);
103                 })
104                 .runWithVerifier(new ColorVerifier(expectedColor));
105     }
106 
107     @Test
testLayerPaintXfermodeWithSoftware()108     public void testLayerPaintXfermodeWithSoftware() {
109         createTest()
110                 .addLayout(R.layout.simple_red_layout, (ViewInitializer) view -> {
111                     Paint paint = new Paint();
112                     paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
113                     view.setLayerType(View.LAYER_TYPE_SOFTWARE, paint);
114                 }, true)
115                 .runWithVerifier(new ColorVerifier(Color.TRANSPARENT));
116     }
117 
118     @Test
testLayerPaintAlphaChanged()119     public void testLayerPaintAlphaChanged() {
120         final CountDownLatch fence = new CountDownLatch(1);
121         createTest()
122             .addLayout(R.layout.frame_layout, view -> {
123                 FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout);
124                 View child = new View(view.getContext());
125                 child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
126                 child.setAlpha(0.0f);
127                 // add rendering content
128                 child.setBackgroundColor(Color.RED);
129                 root.addView(child, new FrameLayout.LayoutParams(TEST_WIDTH, TEST_HEIGHT,
130                         Gravity.TOP | Gravity.LEFT));
131 
132                 // Post non-zero alpha a few frames in, so that the initial layer draw completes.
133                 root.getViewTreeObserver().addOnPreDrawListener(
134                         new ViewTreeObserver.OnPreDrawListener() {
135                             int mDrawCount = 0;
136                             @Override
137                             public boolean onPreDraw() {
138                                 if (mDrawCount++ == 5) {
139                                     root.getChildAt(0).setAlpha(1.00f);
140                                     root.getViewTreeObserver().removeOnPreDrawListener(this);
141                                     root.post(fence::countDown);
142                                 } else {
143                                     root.postInvalidate();
144                                 }
145                                 return true;
146                             }
147                         });
148             }, true, fence)
149             .runWithVerifier(new ColorVerifier(Color.RED));
150     }
151 
152     @Test
testLayerPaintColorFilter()153     public void testLayerPaintColorFilter() {
154         // Red, fully desaturated. Note that it's not 255/3 in each channel.
155         // See ColorMatrix#setSaturation()
156         @ColorInt
157         final int expectedColor = Color.rgb(54, 54, 54);
158         createTest()
159                 .addLayout(R.layout.simple_red_layout, (ViewInitializer) view -> {
160                     Paint paint = new Paint();
161                     ColorMatrix desatMatrix = new ColorMatrix();
162                     desatMatrix.setSaturation(0.0f);
163                     paint.setColorFilter(new ColorMatrixColorFilter(desatMatrix));
164                     view.setLayerType(View.LAYER_TYPE_HARDWARE, paint);
165                 })
166                 .runWithVerifier(new ColorVerifier(expectedColor));
167     }
168 
169     @Test
testLayerPaintBlend()170     public void testLayerPaintBlend() {
171         // Red, drawn underneath opaque white, so output should be white.
172         // TODO: consider doing more interesting blending test here
173         @ColorInt
174         final int expectedColor = Color.WHITE;
175         createTest()
176                 .addLayout(R.layout.simple_red_layout, (ViewInitializer) view -> {
177                     Paint paint = new Paint();
178                     /* Note that when drawing in SW, we're blending within an otherwise empty
179                      * SW layer, as opposed to in the frame buffer (which has a white
180                      * background).
181                      *
182                      * For this reason we use just use DST, which just throws out the SRC
183                      * content, regardless of the DST alpha channel.
184                      */
185                     paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST));
186                     view.setLayerType(View.LAYER_TYPE_HARDWARE, paint);
187                 })
188                 .runWithVerifier(new ColorVerifier(expectedColor));
189     }
190 
191     @LargeTest
192     @Test
testLayerClear()193     public void testLayerClear() {
194         ViewInitializer initializer = new ViewInitializer() {
195             ValueAnimator mAnimator = ValueAnimator.ofInt(0, 20);
196             @Override
197             public void initializeView(View view) {
198                 FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout);
199                 root.setAlpha(0.5f);
200 
201                 final View child = new View(view.getContext());
202                 child.setBackgroundColor(Color.BLUE);
203                 child.setTranslationX(10);
204                 child.setLayoutParams(
205                         new FrameLayout.LayoutParams(50, 50));
206                 child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
207                 root.addView(child);
208 
209                 mAnimator.setRepeatMode(ValueAnimator.REVERSE);
210                 mAnimator.setRepeatCount(ValueAnimator.INFINITE);
211                 mAnimator.setDuration(200);
212                 mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
213                     @Override
214                     public void onAnimationUpdate(ValueAnimator animation) {
215                         child.setTranslationY((Integer) mAnimator.getAnimatedValue());
216                     }
217                 });
218                 mAnimator.start();
219             }
220             @Override
221             public void teardownView() {
222                 mAnimator.cancel();
223             }
224         };
225 
226         createTest()
227                 .addLayout(R.layout.frame_layout, initializer, true)
228                 .runWithAnimationVerifier(new ColorCountVerifier(
229                         Color.WHITE, 90 * 90 - 50 * 50, 10));
230     }
231 
232     @Test
testAlphaLayerChild()233     public void testAlphaLayerChild() {
234         ViewInitializer initializer = new ViewInitializer() {
235             @Override
236             public void initializeView(View view) {
237                 FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout);
238                 root.setAlpha(0.5f);
239 
240                 View child = new View(view.getContext());
241                 child.setBackgroundColor(Color.BLUE);
242                 child.setTranslationX(10);
243                 child.setTranslationY(10);
244                 child.setLayoutParams(
245                         new FrameLayout.LayoutParams(50, 50));
246                 child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
247                 root.addView(child);
248             }
249         };
250 
251         createTest()
252                 .addLayout(R.layout.frame_layout, initializer)
253                 .runWithVerifier(new RectVerifier(Color.WHITE, 0xff8080ff,
254                         new Rect(10, 10, 60, 60)));
255     }
256 
257     @Test
testLayerInitialSizeZero()258     public void testLayerInitialSizeZero() {
259         createTest()
260                 .addLayout(R.layout.frame_layout, view -> {
261                     FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout);
262                     // disable clipChildren, to ensure children aren't rejected by bounds
263                     root.setClipChildren(false);
264                     for (int i = 0; i < 2; i++) {
265                         View child = new View(view.getContext());
266                         child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
267                         // add rendering content, so View isn't skipped at render time
268                         child.setBackgroundColor(Color.RED);
269 
270                         // add one with width=0, one with height=0
271                         root.addView(child, new FrameLayout.LayoutParams(
272                                 i == 0 ? 0 : 90,
273                                 i == 0 ? 90 : 0,
274                                 Gravity.TOP | Gravity.LEFT));
275                     }
276                 }, true)
277                 .runWithVerifier(new ColorVerifier(Color.WHITE, 0 /* zero tolerance */));
278     }
279 
280     @Test
testLayerResizeZero()281     public void testLayerResizeZero() {
282         final CountDownLatch fence = new CountDownLatch(1);
283         createTest()
284                 .addLayout(R.layout.frame_layout, view -> {
285                     FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout);
286                     // disable clipChildren, to ensure child isn't rejected by bounds
287                     root.setClipChildren(false);
288                     for (int i = 0; i < 2; i++) {
289                         View child = new View(view.getContext());
290                         child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
291                         // add rendering content, so View isn't skipped at render time
292                         child.setBackgroundColor(Color.BLUE);
293                         root.addView(child, new FrameLayout.LayoutParams(90, 90,
294                                 Gravity.TOP | Gravity.LEFT));
295                     }
296 
297                     // post invalid dimensions a few frames in, so initial layer allocation succeeds
298                     // NOTE: this must execute before capture, or verification will fail
299                     root.getViewTreeObserver().addOnPreDrawListener(
300                             new ViewTreeObserver.OnPreDrawListener() {
301                         int mDrawCount = 0;
302                         @Override
303                         public boolean onPreDraw() {
304                             if (mDrawCount++ == 5) {
305                                 root.getChildAt(0).getLayoutParams().width = 0;
306                                 root.getChildAt(0).requestLayout();
307                                 root.getChildAt(1).getLayoutParams().height = 0;
308                                 root.getChildAt(1).requestLayout();
309                                 root.getViewTreeObserver().removeOnPreDrawListener(this);
310                                 root.post(fence::countDown);
311                             } else {
312                                 root.postInvalidate();
313                             }
314                             return true;
315                         }
316                     });
317                 }, true, fence)
318                 .runWithVerifier(new ColorVerifier(Color.WHITE, 0 /* zero tolerance */));
319     }
320 
321     @Test
testSaveLayerWithColorFilter()322     public void testSaveLayerWithColorFilter() {
323         // verify that renderer can draw nested clipped layers with chained color filters
324         createTest()
325             .addCanvasClient((canvas, width, height) -> {
326                 Paint redPaint = new Paint();
327                 redPaint.setColor(0xffff0000);
328                 Paint firstLayerPaint = new Paint();
329                 float[] blueToGreenMatrix = new float[20];
330                 blueToGreenMatrix[7] = blueToGreenMatrix[18] = 1.0f;
331                 ColorMatrixColorFilter blueToGreenFilter = new ColorMatrixColorFilter(blueToGreenMatrix);
332                 firstLayerPaint.setColorFilter(blueToGreenFilter);
333                 Paint secondLayerPaint = new Paint();
334                 float[] redToBlueMatrix = new float[20];
335                 redToBlueMatrix[10] = redToBlueMatrix[18] = 1.0f;
336                 ColorMatrixColorFilter redToBlueFilter = new ColorMatrixColorFilter(redToBlueMatrix);
337                 secondLayerPaint.setColorFilter(redToBlueFilter);
338                 // The color filters are applied starting first with the inner layer and then the
339                 // outer layer.
340                 canvas.saveLayer(40, 5, 80, 70, firstLayerPaint);
341                 canvas.saveLayer(5, 40, 70, 80, secondLayerPaint);
342                 canvas.drawRect(10, 10, 70, 70, redPaint);
343                 canvas.restore();
344                 canvas.restore();
345             })
346             .runWithVerifier(new RectVerifier(Color.WHITE, Color.GREEN, new Rect(40, 40, 70, 70)));
347     }
348 
349     @Test
testSaveLayerWithAlpha()350     public void testSaveLayerWithAlpha() {
351         // verify that renderer can draw nested clipped layers with different alpha
352         createTest() // picture mode is disable due to bug:34871089
353             .addCanvasClient((canvas, width, height) -> {
354                 Paint redPaint = new Paint();
355                 redPaint.setColor(0xffff0000);
356                 canvas.saveLayerAlpha(40, 5, 80, 70, 0x7f);
357                 canvas.saveLayerAlpha(5, 40, 70, 80, 0x3f);
358                 canvas.drawRect(10, 10, 70, 70, redPaint);
359                 canvas.restore();
360                 canvas.restore();
361             })
362             .runWithVerifier(new RectVerifier(Color.WHITE, 0xffffE0E0, new Rect(40, 40, 70, 70)));
363     }
364 
365     @Test
testSaveLayerRestoreBehavior()366     public void testSaveLayerRestoreBehavior() {
367         createTest()
368                 .addCanvasClient((canvas, width, height) -> {
369                     //set identity matrix
370                     Matrix identity = new Matrix();
371                     canvas.setMatrix(identity);
372                     final Paint p = new Paint();
373 
374                     canvas.saveLayer(0, 0, width, height, p);
375 
376                     //change matrix and clip to something different
377                     canvas.clipRect(0, 0, width >> 1, height >> 1);
378                     Matrix scaledMatrix = new Matrix();
379                     scaledMatrix.setScale(4, 5);
380                     canvas.setMatrix(scaledMatrix);
381                     assertEquals(scaledMatrix, canvas.getMatrix());
382 
383                     canvas.drawColor(Color.BLUE);
384                     canvas.restore();
385 
386                     //check if identity matrix is restored
387                     assertEquals(identity, canvas.getMatrix());
388 
389                     //should draw to the entire canvas, because clip has been removed
390                     canvas.drawColor(Color.RED);
391                 })
392                 .runWithVerifier(new ColorVerifier(Color.RED));
393     }
394 
395     @LargeTest
396     @Test
testWebViewWithLayer()397     public void testWebViewWithLayer() {
398         if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
399             return; // no WebView to run test on
400         }
401         CountDownLatch hwFence = new CountDownLatch(1);
402         createTest()
403                 .addLayout(R.layout.test_content_webview, (ViewInitializer) view -> {
404                     WebView webview = view.requireViewById(R.id.webview);
405                     WebViewReadyHelper helper = new WebViewReadyHelper(webview, hwFence);
406                     helper.loadData("<body style=\"background-color:blue\">");
407                     webview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
408                 }, true, hwFence)
409                 .runWithVerifier(new ColorVerifier(Color.BLUE));
410     }
411 
412     @LargeTest
413     @Test
testWebViewWithOffsetLayer()414     public void testWebViewWithOffsetLayer() {
415         if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
416             return; // no WebView to run test on
417         }
418         CountDownLatch hwFence = new CountDownLatch(1);
419         createTest()
420                 .addLayout(R.layout.frame_layout_webview, (ViewInitializer) view -> {
421                     FrameLayout layout = view.requireViewById(R.id.frame_layout);
422                     layout.setBackgroundColor(Color.RED);
423 
424                     WebView webview = view.requireViewById(R.id.webview);
425                     WebViewReadyHelper helper = new WebViewReadyHelper(webview, hwFence);
426                     helper.loadData("<body style=\"background-color:blue\">");
427 
428                     webview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
429                     webview.setTranslationX(10);
430                     webview.setTranslationY(10);
431                     webview.getLayoutParams().width = TEST_WIDTH - 20;
432                     webview.getLayoutParams().height = TEST_HEIGHT - 20;
433                 }, true, hwFence)
434                 .runWithVerifier(new RectVerifier(Color.RED, Color.BLUE,
435                         new Rect(10, 10, TEST_WIDTH - 10, TEST_HEIGHT - 10)));
436     }
437 
438     @LargeTest
439     @Test
testWebViewNoOverlappingRenderingAndAlpha()440     public void testWebViewNoOverlappingRenderingAndAlpha() {
441         // Turn off overlapping rendering and apply an alpha. The current behavior is
442         // technically wrong - the alpha is ignored. But the only straightforward way to respect it
443         // would be to promote to a layer, which defeats the purpose of using
444         // forceHasOverlappingRendering, which is to skip promoting to a layer for speed.
445         // If we do ever respect the alpha, the verifier will need to be updated. In the meantime,
446         // this test verifies that we do not crash.
447         if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
448             return; // no WebView to run test on
449         }
450         CountDownLatch hwFence = new CountDownLatch(1);
451         createTest()
452                 .addLayout(R.layout.frame_layout_webview, (ViewInitializer) view -> {
453                     FrameLayout layout = view.requireViewById(R.id.frame_layout);
454                     layout.forceHasOverlappingRendering(false);
455                     layout.setAlpha(.5f);
456 
457                     WebView webview = view.requireViewById(R.id.webview);
458                     WebViewReadyHelper helper = new WebViewReadyHelper(webview, hwFence);
459                     helper.loadData("<body style=\"background-color:blue\">");
460                 }, true, hwFence)
461                 // See comments above. This verifies the current behavior, but more importantly,
462                 // verifies that this does not crash.
463                 .runWithVerifier(new ColorVerifier(Color.BLUE));
464     }
465 
466     @LargeTest
467     @Test
testWebViewWithParentLayer()468     public void testWebViewWithParentLayer() {
469         if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
470             return; // no WebView to run test on
471         }
472         CountDownLatch hwFence = new CountDownLatch(1);
473         createTest()
474                 .addLayout(R.layout.frame_layout_webview, (ViewInitializer) view -> {
475                     FrameLayout layout = view.requireViewById(R.id.frame_layout);
476                     layout.setBackgroundColor(Color.RED);
477                     layout.setLayerType(View.LAYER_TYPE_HARDWARE, null);
478 
479                     WebView webview = view.requireViewById(R.id.webview);
480                     WebViewReadyHelper helper = new WebViewReadyHelper(webview, hwFence);
481                     helper.loadData("<body style=\"background-color:blue\">");
482 
483                     webview.setTranslationX(10);
484                     webview.setTranslationY(10);
485                     webview.getLayoutParams().width = TEST_WIDTH - 20;
486                     webview.getLayoutParams().height = TEST_HEIGHT - 20;
487 
488                 }, true, hwFence)
489                 .runWithVerifier(new RectVerifier(Color.RED, Color.BLUE,
490                         new Rect(10, 10, TEST_WIDTH - 10, TEST_HEIGHT - 10)));
491     }
492 
493     @LargeTest
494     @Test
testWebViewScaledWithParentLayer()495     public void testWebViewScaledWithParentLayer() {
496         if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
497             return; // no WebView to run test on
498         }
499         CountDownLatch hwFence = new CountDownLatch(1);
500         createTest()
501                 .addLayout(R.layout.frame_layout_webview, (ViewInitializer) view -> {
502                     FrameLayout layout = view.requireViewById(R.id.frame_layout);
503                     layout.setBackgroundColor(Color.RED);
504                     layout.setLayerType(View.LAYER_TYPE_HARDWARE, null);
505 
506                     WebView webview = view.requireViewById(R.id.webview);
507                     WebViewReadyHelper helper = new WebViewReadyHelper(webview, hwFence);
508                     helper.loadData("<body style=\"background-color:blue\">");
509 
510                     webview.setTranslationX(10);
511                     webview.setTranslationY(10);
512                     webview.setScaleX(0.5f);
513                     webview.getLayoutParams().width = 40;
514                     webview.getLayoutParams().height = 40;
515 
516                 }, true, hwFence)
517                 .runWithVerifier(new RectVerifier(Color.RED, Color.BLUE,
518                         new Rect(20, 10, 40, 50)));
519     }
520 
521     @LargeTest
522     @Test
testWebViewWithAlpha()523     public void testWebViewWithAlpha() {
524         if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
525             return; // no WebView to run test on
526         }
527         CountDownLatch hwFence = new CountDownLatch(1);
528         createTest()
529                 .addLayout(R.layout.test_content_webview, (ViewInitializer) view -> {
530                     WebView webview = view.requireViewById(R.id.webview);
531                     WebViewReadyHelper helper = new WebViewReadyHelper(webview, hwFence);
532                     helper.loadData("<body style=\"background-color:blue\">");
533 
534                     // reduce alpha by 50%
535                     webview.setAlpha(0.5f);
536 
537                 }, true, hwFence)
538                 .runWithVerifier(new ColorVerifier(Color.rgb(128, 128, 255)));
539     }
540 
541     @LargeTest
542     @Test
testWebViewWithAlphaLayer()543     public void testWebViewWithAlphaLayer() {
544         if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
545             return; // no WebView to run test on
546         }
547         CountDownLatch hwFence = new CountDownLatch(1);
548         createTest()
549                 .addLayout(R.layout.test_content_webview, (ViewInitializer) view -> {
550                     WebView webview = view.requireViewById(R.id.webview);
551                     WebViewReadyHelper helper = new WebViewReadyHelper(webview, hwFence);
552                     helper.loadData("<body style=\"background-color:blue\">");
553 
554                     // reduce alpha by 50%
555                     Paint paint = new Paint();
556                     paint.setAlpha(128);
557                     webview.setLayerType(View.LAYER_TYPE_HARDWARE, paint);
558 
559                     // reduce alpha by another 50% (ensuring two alphas combine correctly)
560                     webview.setAlpha(0.5f);
561 
562                 }, true, hwFence)
563                 .runWithVerifier(new ColorVerifier(Color.rgb(191, 191, 255)));
564     }
565 
566     @LargeTest
567     @Test
testWebViewWithUnclippedLayer()568     public void testWebViewWithUnclippedLayer() {
569         if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
570             return; // no WebView to run test on
571         }
572         CountDownLatch hwFence = new CountDownLatch(1);
573         Point[] testPoints = new Point[] {
574             // solid area
575             new Point(0, 0),
576             new Point(0, TEST_HEIGHT - 1),
577             // fade area
578             new Point(0, TEST_HEIGHT - 10),
579             new Point(0, TEST_HEIGHT - 5)
580         };
581         createTest()
582                 .addLayout(R.layout.test_content_webview, (ViewInitializer) view -> {
583                     WebView webview = view.requireViewById(R.id.webview);
584                     WebViewReadyHelper helper = new WebViewReadyHelper(webview, hwFence);
585                     helper.loadData("<body style=\"min-height: 120vh; background-color:blue\">");
586                     webview.setVerticalFadingEdgeEnabled(true);
587                     webview.setVerticalScrollBarEnabled(false);
588                     webview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
589 
590                     // Adjust Y to match the same gradient percentage, regardless of vertical
591                     // fading edge length.
592                     int verticalFadingEdgeLength = webview.getVerticalFadingEdgeLength();
593                     testPoints[2].y = TEST_HEIGHT
594                         - (int) Math.round(verticalFadingEdgeLength * 10.0 / 42);
595                     testPoints[3].y = TEST_HEIGHT
596                         - (int) Math.round(verticalFadingEdgeLength * 5.0 / 42);
597                 }, true, hwFence)
598                 .runWithVerifier(new SamplePointVerifier(
599                         testPoints,
600                         new int[] {
601                                 Color.BLUE,
602                                 Color.WHITE,
603                                 0xffc5c5ff, // white blended with blue
604                                 0xffdbdbff  // white blended with blue
605                         }, 50));
606     }
607 
608     @LargeTest
609     @Test
testWebViewWithUnclippedLayerAndComplexClip()610     public void testWebViewWithUnclippedLayerAndComplexClip() {
611         if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
612             return; // no WebView to run test on
613         }
614         CountDownLatch hwFence = new CountDownLatch(1);
615         Point[] testPoints = new Point[] {
616             // solid white area
617             new Point(0, 0),
618             new Point(0, TEST_HEIGHT - 1),
619             // solid blue area
620             new Point(TEST_WIDTH / 2 , 5),
621             // fade area
622             new Point(TEST_WIDTH / 2, TEST_HEIGHT - 10),
623             new Point(TEST_WIDTH / 2, TEST_HEIGHT - 5)
624         };
625         createTest()
626                 .addLayout(R.layout.circle_clipped_webview, (ViewInitializer) view -> {
627                     WebView webview = view.requireViewById(R.id.webview);
628                     WebViewReadyHelper helper = new WebViewReadyHelper(webview, hwFence);
629                     helper.loadData("<body style=\"min-height: 120vh; background-color:blue\">");
630                     webview.setVerticalFadingEdgeEnabled(true);
631                     webview.setVerticalScrollBarEnabled(false);
632                     webview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
633                     // Adjust Y to match the same gradient percentage, regardless of vertical
634                     // fading edge length.
635                     int verticalFadingEdgeLength = webview.getVerticalFadingEdgeLength();
636                     testPoints[3].y = TEST_HEIGHT
637                         - (int) Math.round(verticalFadingEdgeLength * 10.0 / 42);
638                     testPoints[4].y = TEST_HEIGHT
639                         - (int) Math.round(verticalFadingEdgeLength * 5.0 / 42);
640                 }, true, hwFence)
641                 .runWithVerifier(new SamplePointVerifier(
642                         testPoints,
643                         new int[] {
644                                 Color.WHITE,
645                                 Color.WHITE,
646                                 Color.BLUE,
647                                 0xffc5c5ff, // white blended with blue
648                                 0xffdbdbff  // white blended with blue
649                         }, 50));
650     }
651 
652     @LargeTest
653     @Test
testWebViewOnHWLayerAndComplexAntiAliasedClip()654     public void testWebViewOnHWLayerAndComplexAntiAliasedClip() {
655         if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
656             return; // no WebView to run test on
657         }
658 
659         CountDownLatch hwFence = new CountDownLatch(1);
660         createTest()
661                 // golden client - draw a simple non-AA circle
662                 .addCanvasClient((canvas, width, height) -> {
663                     Paint paint = new Paint();
664                     paint.setAntiAlias(true);
665                     paint.setColor(Color.BLUE);
666                     canvas.drawOval(0, 0, width, height, paint);
667                 }, false)
668                 // verify against solid color webview, clipped to its parent oval
669                 .addLayout(R.layout.circle_clipped_webview, (ViewInitializer) view -> {
670                     FrameLayout layout = view.requireViewById(R.id.circle_clip_frame_layout);
671                     WebView webview = view.requireViewById(R.id.webview);
672                     // Promote the webview onto its own layer
673                     webview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
674                     WebViewReadyHelper helper = new WebViewReadyHelper(webview, hwFence);
675                     helper.loadData("<body style=\"background-color:blue\">");
676 
677                 }, true, hwFence)
678                 .runWithComparer(new MSSIMComparer(0.98));
679     }
680 
681     @LargeTest
682     @Test
testWebViewWithParentLayerAndComplexClip()683     public void testWebViewWithParentLayerAndComplexClip() {
684         if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
685             return; // no WebView to run test on
686         }
687 
688         CountDownLatch hwFence = new CountDownLatch(1);
689         createTest()
690                 // golden client - draw a simple AA circle
691                 .addCanvasClient((canvas, width, height) -> {
692                     Paint paint = new Paint();
693                     paint.setAntiAlias(true);
694                     paint.setColor(Color.BLUE);
695                     canvas.drawOval(0, 0, width, height, paint);
696                 }, false)
697                 // verify against solid color webview, clipped to its parent oval
698                 .addLayout(R.layout.circle_clipped_webview, (ViewInitializer) view -> {
699                     FrameLayout layout = view.requireViewById(R.id.circle_clip_frame_layout);
700                     layout.setLayerType(View.LAYER_TYPE_HARDWARE, null);
701 
702                     WebView webview = view.requireViewById(R.id.webview);
703                     WebViewReadyHelper helper = new WebViewReadyHelper(webview, hwFence);
704                     helper.loadData("<body style=\"background-color:blue\">");
705 
706                 }, true, hwFence)
707                 // WebView is not on its own layer, so the parent clip may not be AA
708                 .runWithComparer(new MSSIMComparer(0.93));
709     }
710 
711     @LargeTest
712     @Test
testWebViewWithRRectClip()713     public void testWebViewWithRRectClip() {
714         if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
715             return; // no WebView to run test on
716         }
717 
718         CountDownLatch hwFence = new CountDownLatch(1);
719         createTest()
720                 // golden client - draw an AA rounded rect
721                 .addCanvasClient((canvas, width, height) -> {
722                     Paint paint = new Paint();
723                     paint.setAntiAlias(true);
724                     paint.setColor(Color.BLUE);
725                     canvas.drawRoundRect(0, 0, width, height, ActivityTestBase.TEST_WIDTH / 4,
726                             ActivityTestBase.TEST_HEIGHT / 4, paint);
727                 }, false)
728                 // verify against solid color webview, which applies a rounded rect clip
729                 .addLayout(R.layout.webview_canvas_rrect_clip, (ViewInitializer) view -> {
730                     WebView webview = view.requireViewById(R.id.webview_canvas_rrect_clip);
731                     WebViewReadyHelper helper = new WebViewReadyHelper(webview, hwFence);
732                     helper.loadData("<body style=\"background-color:blue\">");
733                 }, true, hwFence)
734                 .runWithComparer(new MSSIMComparer(0.90));
735     }
736 }
737