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.transition.cts;
17 
18 import static com.android.compatibility.common.util.CtsMockitoUtils.within;
19 
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertNull;
23 import static org.mockito.Matchers.any;
24 import static org.mockito.Mockito.timeout;
25 import static org.mockito.Mockito.times;
26 import static org.mockito.Mockito.verify;
27 
28 import android.graphics.Color;
29 import android.graphics.Matrix;
30 import android.graphics.drawable.ColorDrawable;
31 import android.graphics.drawable.Drawable;
32 import android.transition.ChangeImageTransform;
33 import android.transition.TransitionManager;
34 import android.util.DisplayMetrics;
35 import android.util.TypedValue;
36 import android.view.ViewGroup;
37 import android.view.ViewGroup.LayoutParams;
38 import android.widget.ImageView;
39 import android.widget.ImageView.ScaleType;
40 
41 import androidx.test.filters.MediumTest;
42 import androidx.test.runner.AndroidJUnit4;
43 
44 import org.junit.Before;
45 import org.junit.Test;
46 import org.junit.runner.RunWith;
47 import org.mockito.Mockito;
48 
49 import java.util.ArrayList;
50 import java.util.List;
51 import java.util.Objects;
52 
53 @MediumTest
54 @RunWith(AndroidJUnit4.class)
55 public class ChangeImageTransformTest extends BaseTransitionTest {
56     ChangeImageTransform mChangeImageTransform;
57     Matrix mStartMatrix;
58     Matrix mEndMatrix;
59     Drawable mImage;
60     ImageView mImageView;
61 
62     @Override
63     @Before
setup()64     public void setup() {
65         super.setup();
66         resetTransition();
67         mStartMatrix = null;
68         mEndMatrix = null;
69         mImage = null;
70         mImageView = null;
71     }
72 
resetTransition()73     private void resetTransition() {
74         mChangeImageTransform = new ChangeImageTransform();
75         mChangeImageTransform.setDuration(500);
76         mTransition = mChangeImageTransform;
77         resetListener();
78     }
79 
80     @Test
testCenterToFitXY()81     public void testCenterToFitXY() throws Throwable {
82         transformImage(ScaleType.CENTER, ScaleType.FIT_XY);
83         verifyMatrixMatches(centerMatrix(), mStartMatrix);
84         verifyMatrixMatches(fitXYMatrix(), mEndMatrix);
85     }
86 
87     @Test
testCenterCropToFitCenter()88     public void testCenterCropToFitCenter() throws Throwable {
89         transformImage(ScaleType.CENTER_CROP, ScaleType.FIT_CENTER);
90         verifyMatrixMatches(centerCropMatrix(), mStartMatrix);
91         verifyMatrixMatches(fitCenterMatrix(), mEndMatrix);
92     }
93 
94     @Test
testCenterInsideToFitEnd()95     public void testCenterInsideToFitEnd() throws Throwable {
96         transformImage(ScaleType.CENTER_INSIDE, ScaleType.FIT_END);
97         // CENTER_INSIDE and CENTER are the same when the image is smaller than the View
98         verifyMatrixMatches(centerMatrix(), mStartMatrix);
99         verifyMatrixMatches(fitEndMatrix(), mEndMatrix);
100     }
101 
102     @Test
testFitStartToCenter()103     public void testFitStartToCenter() throws Throwable {
104         transformImage(ScaleType.FIT_START, ScaleType.CENTER);
105         verifyMatrixMatches(fitStartMatrix(), mStartMatrix);
106         verifyMatrixMatches(centerMatrix(), mEndMatrix);
107     }
108 
109     @Test
testNoChange()110     public void testNoChange() throws Throwable {
111         transformImage(ScaleType.CENTER, ScaleType.CENTER);
112         verifyMatrixMatches(centerMatrix(), mStartMatrix);
113         verifyMatrixMatches(centerMatrix(), mEndMatrix);
114     }
115 
116     @Test
testNoAnimationForDrawableWithoutSize()117     public void testNoAnimationForDrawableWithoutSize() throws Throwable {
118         transformImage(ScaleType.CENTER_CROP, ScaleType.FIT_XY, new ColorDrawable(Color.WHITE),
119                 false, false, true);
120         Matrix identityMatrix = new Matrix();
121         assertEquals(identityMatrix, mStartMatrix);
122         assertEquals(identityMatrix, mEndMatrix);
123     }
124 
125     @Test
testNullAnimatorKeepsImagePadding()126     public void testNullAnimatorKeepsImagePadding() throws Throwable {
127         transformImage(ScaleType.FIT_XY, ScaleType.FIT_XY, new ColorDrawable(Color.WHITE),
128                 true, true, true);
129         assertEquals(mImage.getBounds().width(), mImageView.getWidth()
130                 - mImageView.getPaddingLeft() - mImageView.getPaddingRight());
131         assertEquals(mImage.getBounds().height(), mImageView.getHeight()
132                 - mImageView.getPaddingTop() - mImageView.getPaddingBottom());
133     }
134 
centerMatrix()135     private Matrix centerMatrix() {
136         int imageWidth = mImage.getIntrinsicWidth();
137         int imageViewWidth = mImageView.getWidth();
138         float tx = Math.round((imageViewWidth - imageWidth)/2f);
139 
140         int imageHeight = mImage.getIntrinsicHeight();
141         int imageViewHeight = mImageView.getHeight();
142         float ty = Math.round((imageViewHeight - imageHeight)/2f);
143 
144         Matrix matrix = new Matrix();
145         matrix.postTranslate(tx, ty);
146         return matrix;
147     }
148 
fitXYMatrix()149     private Matrix fitXYMatrix() {
150         int imageWidth = mImage.getIntrinsicWidth();
151         int imageViewWidth = mImageView.getWidth();
152         float scaleX = ((float)imageViewWidth)/imageWidth;
153 
154         int imageHeight = mImage.getIntrinsicHeight();
155         int imageViewHeight = mImageView.getHeight();
156         float scaleY = ((float)imageViewHeight)/imageHeight;
157 
158         Matrix matrix = new Matrix();
159         matrix.postScale(scaleX, scaleY);
160         return matrix;
161     }
162 
centerCropMatrix()163     private Matrix centerCropMatrix() {
164         int imageWidth = mImage.getIntrinsicWidth();
165         int imageViewWidth = mImageView.getWidth();
166         float scaleX = ((float)imageViewWidth)/imageWidth;
167 
168         int imageHeight = mImage.getIntrinsicHeight();
169         int imageViewHeight = mImageView.getHeight();
170         float scaleY = ((float)imageViewHeight)/imageHeight;
171 
172         float maxScale = Math.max(scaleX, scaleY);
173 
174         float width = imageWidth * maxScale;
175         float height = imageHeight * maxScale;
176         float tx = Math.round((imageViewWidth - width) / 2f);
177         float ty = Math.round((imageViewHeight - height) / 2f);
178 
179         Matrix matrix = new Matrix();
180         matrix.postScale(maxScale, maxScale);
181         matrix.postTranslate(tx, ty);
182         return matrix;
183     }
184 
fitCenterMatrix()185     private Matrix fitCenterMatrix() {
186         int imageWidth = mImage.getIntrinsicWidth();
187         int imageViewWidth = mImageView.getWidth();
188         float scaleX = ((float)imageViewWidth)/imageWidth;
189 
190         int imageHeight = mImage.getIntrinsicHeight();
191         int imageViewHeight = mImageView.getHeight();
192         float scaleY = ((float)imageViewHeight)/imageHeight;
193 
194         float minScale = Math.min(scaleX, scaleY);
195 
196         float width = imageWidth * minScale;
197         float height = imageHeight * minScale;
198         float tx = (imageViewWidth - width) / 2f;
199         float ty = (imageViewHeight - height) / 2f;
200 
201         Matrix matrix = new Matrix();
202         matrix.postScale(minScale, minScale);
203         matrix.postTranslate(tx, ty);
204         return matrix;
205     }
206 
fitStartMatrix()207     private Matrix fitStartMatrix() {
208         int imageWidth = mImage.getIntrinsicWidth();
209         int imageViewWidth = mImageView.getWidth();
210         float scaleX = ((float)imageViewWidth)/imageWidth;
211 
212         int imageHeight = mImage.getIntrinsicHeight();
213         int imageViewHeight = mImageView.getHeight();
214         float scaleY = ((float)imageViewHeight)/imageHeight;
215 
216         float minScale = Math.min(scaleX, scaleY);
217 
218         Matrix matrix = new Matrix();
219         matrix.postScale(minScale, minScale);
220         return matrix;
221     }
222 
fitEndMatrix()223     private Matrix fitEndMatrix() {
224         int imageWidth = mImage.getIntrinsicWidth();
225         int imageViewWidth = mImageView.getWidth();
226         float scaleX = ((float)imageViewWidth)/imageWidth;
227 
228         int imageHeight = mImage.getIntrinsicHeight();
229         int imageViewHeight = mImageView.getHeight();
230         float scaleY = ((float)imageViewHeight)/imageHeight;
231 
232         float minScale = Math.min(scaleX, scaleY);
233 
234         float width = imageWidth * minScale;
235         float height = imageHeight * minScale;
236         float tx = imageViewWidth - width;
237         float ty = imageViewHeight - height;
238 
239         Matrix matrix = new Matrix();
240         matrix.postScale(minScale, minScale);
241         matrix.postTranslate(tx, ty);
242         return matrix;
243     }
244 
verifyMatrixMatches(Matrix expected, Matrix matrix)245     private void verifyMatrixMatches(Matrix expected, Matrix matrix) {
246         if (expected == null) {
247             assertNull(matrix);
248             return;
249         }
250         assertNotNull(matrix);
251         float[] expectedValues = new float[9];
252         expected.getValues(expectedValues);
253 
254         float[] values = new float[9];
255         matrix.getValues(values);
256 
257         for (int i = 0; i < values.length; i++) {
258             final float expectedValue = expectedValues[i];
259             final float value = values[i];
260             assertEquals("Value [" + i + "]", expectedValue, value, 0.01f);
261         }
262     }
263 
transformImage(final ScaleType startScale, final ImageView.ScaleType endScale)264     private void transformImage(final ScaleType startScale,
265             final ImageView.ScaleType endScale) throws Throwable {
266         transformImage(startScale, endScale, null, false, false, startScale == endScale);
267     }
268 
transformImage(final ScaleType startScale, final ImageView.ScaleType endScale, final Drawable customImage, final boolean applyPadding, final boolean withChangingSize, final boolean noMatrixChangeExpected)269     private void transformImage(final ScaleType startScale,
270             final ImageView.ScaleType endScale,
271             final Drawable customImage,
272             final boolean applyPadding,
273             final boolean withChangingSize,
274             final boolean noMatrixChangeExpected) throws Throwable {
275         final ImageView imageView = enterImageViewScene(startScale, customImage, applyPadding);
276         final List<Matrix> matrices = watchImageMatrix(imageView);
277 
278         mActivityRule.runOnUiThread(() -> {
279             imageView.invalidate();
280         });
281 
282         // Wait for one draw() call
283         verify(matrices, within(5000)).add(any());
284 
285         mActivityRule.runOnUiThread(() -> {
286             TransitionManager.beginDelayedTransition(mSceneRoot, mChangeImageTransform);
287             if (withChangingSize) {
288                 imageView.getLayoutParams().height /= 2;
289                 imageView.requestLayout();
290             }
291             imageView.setScaleType(endScale);
292             imageView.invalidate();
293         });
294         waitForStart();
295         waitForEnd(5000);
296         if (noMatrixChangeExpected) {
297             verify(matrices, times(1)).add(any());
298             assertEquals(1, matrices.size());
299         } else {
300             verify(matrices, timeout(5000).atLeast(3)).add(any());
301         }
302         mStartMatrix = matrices.get(0);
303         mEndMatrix = matrices.get(matrices.size() - 1);
304     }
305 
watchImageMatrix(ImageView view)306     private List<Matrix> watchImageMatrix(ImageView view) throws Throwable {
307         final List<Matrix> matrices = Mockito.spy(new ArrayList<>());
308         mActivityRule.runOnUiThread(() -> {
309             mActivity.getWindow().getDecorView().getViewTreeObserver().addOnDrawListener(() -> {
310                 Matrix matrix = view.getImageMatrix();
311                 if (matrices.isEmpty()
312                         || !Objects.equals(matrix, matrices.get(matrices.size() - 1))) {
313                     if (matrix == null) {
314                         matrices.add(matrix);
315                     } else {
316                         matrices.add(new Matrix(matrix));
317                     }
318                 }
319             });
320         });
321         return matrices;
322     }
323 
enterImageViewScene(final ScaleType scaleType, final Drawable customImage, final boolean withPadding)324     private ImageView enterImageViewScene(final ScaleType scaleType,
325             final Drawable customImage,
326             final boolean withPadding) throws Throwable {
327         enterScene(R.layout.scene4);
328         final ViewGroup container = mActivity.findViewById(R.id.holder);
329         final ImageView[] imageViews = new ImageView[1];
330         mActivityRule.runOnUiThread(() -> {
331             mImageView = new ImageView(mActivity);
332             if (customImage != null) {
333                 mImage = customImage;
334             } else {
335                 mImage = mActivity.getDrawable(android.R.drawable.ic_media_play);
336             }
337             mImageView.setImageDrawable(mImage);
338             mImageView.setScaleType(scaleType);
339             imageViews[0] = mImageView;
340             container.addView(mImageView);
341             LayoutParams layoutParams = mImageView.getLayoutParams();
342             DisplayMetrics metrics = mActivity.getResources().getDisplayMetrics();
343             float size = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, metrics);
344             layoutParams.width = Math.round(size);
345             layoutParams.height = Math.round(size * 2);
346             mImageView.setLayoutParams(layoutParams);
347             if (withPadding) {
348                 int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8,
349                         metrics);
350                 mImageView.setPadding(padding, padding, padding, padding);
351             }
352         });
353         mInstrumentation.waitForIdleSync();
354         return imageViews[0];
355     }
356 }
357 
358