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
17package android.uirendering.cts.testclasses;
18
19import android.animation.Animator;
20import android.animation.ValueAnimator;
21import android.content.Context;
22import android.graphics.Bitmap;
23import android.graphics.Canvas;
24import android.graphics.Color;
25import android.graphics.Rect;
26import android.os.Handler;
27import android.support.test.filters.LargeTest;
28import android.support.test.filters.MediumTest;
29import android.support.test.runner.AndroidJUnit4;
30import android.uirendering.cts.R;
31import android.uirendering.cts.bitmapcomparers.MSSIMComparer;
32import android.uirendering.cts.bitmapverifiers.BitmapVerifier;
33import android.uirendering.cts.bitmapverifiers.ColorCountVerifier;
34import android.uirendering.cts.testinfrastructure.ActivityTestBase;
35import android.uirendering.cts.testinfrastructure.ViewInitializer;
36import android.view.FrameMetrics;
37import android.view.View;
38import android.view.ViewAnimationUtils;
39import android.view.Window;
40import android.widget.FrameLayout;
41
42import org.junit.Assert;
43import org.junit.Test;
44import org.junit.runner.RunWith;
45
46import java.util.Arrays;
47
48@LargeTest
49@RunWith(AndroidJUnit4.class)
50public class BitmapTests extends ActivityTestBase {
51    class BitmapView extends View {
52        private Bitmap mBitmap;
53        private int mColor;
54
55        public BitmapView(Context context) {
56            super(context);
57            mBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
58            setColor(Color.BLUE);
59        }
60
61        @Override
62        protected void onDraw(Canvas canvas) {
63            canvas.drawBitmap(mBitmap, new Rect(0, 0, 1, 1), canvas.getClipBounds(), null);
64        }
65
66        public void setColor(int color) {
67            mColor = color;
68            mBitmap.setPixel(0, 0, color);
69        }
70
71        public int getColor() {
72            return mColor;
73        }
74    }
75
76    /*
77     * The following test verifies that bitmap changes during render thread animation won't
78     * be visible: we changed a bitmap from blue to red during circular reveal (an RT animation),
79     * and changed it back to blue before the end of the animation; we should never see any
80     * red pixel.
81     */
82    @Test
83    public void testChangeDuringRtAnimation() {
84        class RtOnlyFrameCounter implements Window.OnFrameMetricsAvailableListener {
85            private int count = 0;
86
87            @Override
88            public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics,
89                    int dropCountSinceLastInvocation) {
90                if (frameMetrics.getMetric(FrameMetrics.ANIMATION_DURATION) == 0
91                        && frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION) == 0
92                        && frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION) == 0) {
93                    count++;
94                };
95            }
96
97            public boolean isLargeEnough() {
98                return count >= 5;
99            }
100        }
101
102        ViewInitializer initializer = new ViewInitializer() {
103            Animator mAnimator;
104            RtOnlyFrameCounter mCounter = new RtOnlyFrameCounter();
105
106            @Override
107            public void initializeView(View view) {
108                FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout);
109
110                final BitmapView child = new BitmapView(view.getContext());
111                child.setLayoutParams(new FrameLayout.LayoutParams(50, 50));
112                root.addView(child);
113
114                mAnimator = ViewAnimationUtils.createCircularReveal(child, 0, 0, 0, 90);
115                mAnimator.setDuration(3000);
116                mAnimator.start();
117
118                Handler handler = new Handler();
119                handler.postDelayed(new Runnable() {
120                    @Override
121                    public void run() {
122                        child.setColor(Color.RED);
123                        try {
124                            Thread.sleep(1000);
125                        } catch (Exception e) {
126                            // do nothing
127                        }
128                        child.setColor(Color.BLUE);
129                    }
130                }, 1000);
131                getActivity().getWindow().addOnFrameMetricsAvailableListener(mCounter, handler);
132            }
133
134            @Override
135            public void teardownView() {
136                mAnimator.cancel();
137                Assert.assertTrue(mCounter.isLargeEnough());
138            }
139        };
140
141        createTest()
142                .addLayout(R.layout.frame_layout, initializer, true)
143                .runWithAnimationVerifier(new ColorCountVerifier(Color.RED, 0));
144    }
145
146    /*
147     * The following test verifies that bitmap changes during UI thread animation are
148     * visible: we keep changing a bitmap's color between red and blue in sync with the
149     * background, and we should only see pure blue or red.
150    */
151    @Test
152    public void testChangeDuringUiAnimation() {
153        class BlueOrRedVerifier extends BitmapVerifier {
154            @Override
155            public boolean verify(int[] bitmap, int offset, int stride, int width, int height) {
156                MSSIMComparer comparer = new MSSIMComparer(0.99);
157                int[] red  = new int[offset + height * stride];
158                Arrays.fill(red, Color.RED);
159                int[] blue  = new int[offset + height * stride];
160                Arrays.fill(blue, Color.BLUE);
161                boolean isRed = comparer.verifySame(red, bitmap, offset, stride, width, height);
162                boolean isBlue = comparer.verifySame(blue, bitmap, offset, stride, width, height);
163                return isRed || isBlue;
164            }
165        }
166
167        ViewInitializer initializer = new ViewInitializer() {
168            ValueAnimator mAnimator;
169
170            @Override
171            public void initializeView(View view) {
172                FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout);
173                root.setBackgroundColor(Color.BLUE);
174
175                final BitmapView child = new BitmapView(view.getContext());
176
177                // The child size is strictly less than the test canvas size,
178                // and we are moving it up and down inside the canvas.
179                child.setLayoutParams(new FrameLayout.LayoutParams(ActivityTestBase.TEST_WIDTH / 2,
180                        ActivityTestBase.TEST_HEIGHT / 2));
181                root.addView(child);
182                child.setColor(Color.BLUE);
183
184                mAnimator = ValueAnimator.ofInt(0, ActivityTestBase.TEST_HEIGHT / 2);
185                mAnimator.setRepeatMode(mAnimator.REVERSE);
186                mAnimator.setRepeatCount(mAnimator.INFINITE);
187                mAnimator.setDuration(400);
188                mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
189                    @Override
190                    public void onAnimationUpdate(ValueAnimator animation) {
191                        int v = (Integer) mAnimator.getAnimatedValue();
192                        child.setTranslationY(v);
193                        if (child.getColor() == Color.BLUE) {
194                            root.setBackgroundColor(Color.RED);
195                            child.setColor(Color.RED);
196                        } else {
197                            root.setBackgroundColor(Color.BLUE);
198                            child.setColor(Color.BLUE);
199                        }
200                    }
201                });
202                mAnimator.start();
203            }
204
205            @Override
206            public void teardownView() {
207                mAnimator.cancel();
208            }
209        };
210
211        createTest()
212                .addLayout(R.layout.frame_layout, initializer, true)
213                .runWithAnimationVerifier(new BlueOrRedVerifier());
214    }
215}
216