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