blob: 40ba46b4447fb921e87a0f6e4afe55f7a40d8a44 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.transition.cts;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import android.animation.Animator;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
import android.transition.ChangeBounds;
import android.transition.Transition;
import android.transition.TransitionValues;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.LinearInterpolator;
import com.android.compatibility.common.util.PollingCheck;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@MediumTest
@RunWith(AndroidJUnit4.class)
public class ChangeBoundsTest extends BaseTransitionTest {
private static final int SMALL_SQUARE_SIZE_DP = 30;
private static final int LARGE_SQUARE_SIZE_DP = 50;
private static final int SMALL_OFFSET_DP = 2;
ChangeBounds mChangeBounds;
ValidateBoundsListener mBoundsChangeListener;
@Override
@Before
public void setup() {
super.setup();
resetChangeBoundsTransition();
mBoundsChangeListener = null;
}
private void resetChangeBoundsTransition() {
mListener = mock(Transition.TransitionListener.class);
mChangeBounds = new MyChangeBounds();
mChangeBounds.setDuration(400);
mChangeBounds.addListener(mListener);
mChangeBounds.setInterpolator(new LinearInterpolator());
mTransition = mChangeBounds;
}
@Test
public void testBasicChangeBounds() throws Throwable {
enterScene(R.layout.scene1);
validateInScene1();
mBoundsChangeListener = new ValidateBoundsListener(true);
startTransition(R.layout.scene6);
// The update listener will validate that it is changing throughout the animation
waitForEnd(800);
validateInScene6();
}
@Test
public void testResizeClip() throws Throwable {
assertEquals(false, mChangeBounds.getResizeClip());
mChangeBounds.setResizeClip(true);
assertEquals(true, mChangeBounds.getResizeClip());
enterScene(R.layout.scene1);
validateInScene1();
mBoundsChangeListener = new ValidateBoundsListener(true);
startTransition(R.layout.scene6);
// The update listener will validate that it is changing throughout the animation
waitForEnd(800);
validateInScene6();
}
@Test
public void testResizeClipSmaller() throws Throwable {
mChangeBounds.setResizeClip(true);
enterScene(R.layout.scene6);
validateInScene6();
mBoundsChangeListener = new ValidateBoundsListener(false);
startTransition(R.layout.scene1);
// The update listener will validate that it is changing throughout the animation
waitForEnd(800);
validateInScene1();
}
@Test
public void testInterruptSameDestination() throws Throwable {
enterScene(R.layout.scene1);
validateInScene1();
startTransition(R.layout.scene6);
waitForMiddleOfTransition();
resetChangeBoundsTransition();
startTransition(R.layout.scene6);
assertFalse(isRestartingAnimation());
waitForEnd(1000);
validateInScene6();
}
@Test
public void testInterruptSameDestinationResizeClip() throws Throwable {
mChangeBounds.setResizeClip(true);
enterScene(R.layout.scene1);
validateInScene1();
startTransition(R.layout.scene6);
waitForMiddleOfTransition();
resetChangeBoundsTransition();
mChangeBounds.setResizeClip(true);
startTransition(R.layout.scene6);
assertFalse(isRestartingAnimation());
assertFalse(isRestartingClip());
waitForEnd(1000);
validateInScene6();
}
@Test
public void testInterruptWithReverse() throws Throwable {
enterScene(R.layout.scene1);
validateInScene1();
startTransition(R.layout.scene6);
waitForMiddleOfTransition();
// reverse the transition back to scene1
resetChangeBoundsTransition();
startTransition(R.layout.scene1);
assertFalse(isRestartingAnimation());
waitForEnd(1000);
validateInScene1();
}
@Test
public void testInterruptWithReverseResizeClip() throws Throwable {
mChangeBounds.setResizeClip(true);
enterScene(R.layout.scene1);
validateInScene1();
startTransition(R.layout.scene6);
waitForMiddleOfTransition();
// reverse the transition back to scene1
resetChangeBoundsTransition();
mChangeBounds.setResizeClip(true);
startTransition(R.layout.scene1);
assertFalse(isRestartingAnimation());
assertFalse(isRestartingClip());
waitForEnd(1000);
validateInScene1();
}
private void waitForMiddleOfTransition() throws Throwable {
Resources resources = mActivity.getResources();
float closestDistance = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
SMALL_SQUARE_SIZE_DP / 2, resources.getDisplayMetrics());
final View red = mActivity.findViewById(R.id.redSquare);
final View green = mActivity.findViewById(R.id.greenSquare);
PollingCheck.waitFor(
() -> red.getTop() > closestDistance && green.getTop() > closestDistance);
assertTrue(red.getTop() > closestDistance);
assertTrue(green.getTop() > closestDistance);
}
private boolean isRestartingAnimation() {
View red = mActivity.findViewById(R.id.redSquare);
View green = mActivity.findViewById(R.id.greenSquare);
Resources resources = mActivity.getResources();
float closestDistance = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
SMALL_OFFSET_DP, resources.getDisplayMetrics());
return red.getTop() < closestDistance || green.getTop() < closestDistance;
}
private boolean isRestartingClip() {
Resources resources = mActivity.getResources();
float smallDim = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
SMALL_SQUARE_SIZE_DP + SMALL_OFFSET_DP, resources.getDisplayMetrics());
float largeDim = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
LARGE_SQUARE_SIZE_DP - SMALL_OFFSET_DP, resources.getDisplayMetrics());
View red = mActivity.findViewById(R.id.redSquare);
Rect redClip = red.getClipBounds();
View green = mActivity.findViewById(R.id.greenSquare);
Rect greenClip = green.getClipBounds();
return redClip == null || redClip.width() < smallDim || redClip.width() > largeDim ||
greenClip == null || greenClip.width() < smallDim || greenClip.width() > largeDim;
}
private void validateInScene1() {
validateViewPlacement(R.id.redSquare, R.id.greenSquare, SMALL_SQUARE_SIZE_DP);
}
private void validateInScene6() {
validateViewPlacement(R.id.greenSquare, R.id.redSquare, LARGE_SQUARE_SIZE_DP);
}
private void validateViewPlacement(int topViewResource, int bottomViewResource, int dim) {
Resources resources = mActivity.getResources();
float expectedDim = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dim,
resources.getDisplayMetrics());
View aboveSquare = mActivity.findViewById(topViewResource);
assertEquals(0, aboveSquare.getLeft());
assertEquals(0, aboveSquare.getTop());
assertTrue(aboveSquare.getRight() != 0);
final int aboveSquareBottom = aboveSquare.getBottom();
assertTrue(aboveSquareBottom != 0);
View belowSquare = mActivity.findViewById(bottomViewResource);
assertEquals(0, belowSquare.getLeft());
assertWithinAPixel(aboveSquareBottom, belowSquare.getTop());
assertWithinAPixel(aboveSquareBottom + aboveSquare.getHeight(),
belowSquare.getBottom());
assertWithinAPixel(aboveSquare.getRight(), belowSquare.getRight());
assertWithinAPixel(expectedDim, aboveSquare.getHeight());
assertWithinAPixel(expectedDim, aboveSquare.getWidth());
assertWithinAPixel(expectedDim, belowSquare.getHeight());
assertWithinAPixel(expectedDim, belowSquare.getWidth());
assertNull(aboveSquare.getClipBounds());
assertNull(belowSquare.getClipBounds());
}
private static boolean isWithinAPixel(float expectedDim, int dim) {
return (Math.abs(dim - expectedDim) <= 1);
}
private static void assertWithinAPixel(float expectedDim, int dim) {
assertTrue("Expected dimension to be within one pixel of "
+ expectedDim + ", but was " + dim, isWithinAPixel(expectedDim, dim));
}
private static void assertNotWithinAPixel(float expectedDim, int dim) {
assertTrue("Expected dimension to not be within one pixel of "
+ expectedDim + ", but was " + dim, !isWithinAPixel(expectedDim, dim));
}
private class MyChangeBounds extends ChangeBounds {
private static final String PROPNAME_BOUNDS = "android:changeBounds:bounds";
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
TransitionValues endValues) {
Animator animator = super.createAnimator(sceneRoot, startValues, endValues);
if (animator != null && mBoundsChangeListener != null) {
animator.addListener(mBoundsChangeListener);
Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS);
Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
}
return animator;
}
}
private class ValidateBoundsListener implements ViewTreeObserver.OnDrawListener,
Animator.AnimatorListener {
final boolean mGrow;
final int mMin;
final int mMax;
final Point mRedDimensions = new Point(-1, -1);
final Point mGreenDimensions = new Point(-1, -1);
View mRedSquare;
View mGreenSquare;
boolean mDidChangeSize;
private ValidateBoundsListener(boolean grow) {
mGrow = grow;
Resources resources = mActivity.getResources();
mMin = (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
SMALL_SQUARE_SIZE_DP, resources.getDisplayMetrics()));
mMax = (int) Math.ceil(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
LARGE_SQUARE_SIZE_DP, resources.getDisplayMetrics()));
}
public void validateView(View view, Point dimensions) {
final String name = view.getTransitionName();
final boolean clipped = mChangeBounds.getResizeClip();
assertEquals(clipped, view.getClipBounds() != null);
final int width;
final int height;
if (clipped) {
width = view.getClipBounds().width();
height = view.getClipBounds().height();
} else {
width = view.getWidth();
height = view.getHeight();
}
int newWidth = validateDim(name, "width", dimensions.x, width);
int newHeight = validateDim(name, "height", dimensions.y, height);
dimensions.set(newWidth, newHeight);
}
private int validateDim(String name, String dimen, int lastDim, int newDim) {
int dim = newDim;
if (lastDim != -1) {
// We must give a pixel's buffer because the top-left and
// bottom-right may move independently, causing a rounding error
// in size change.
if (mGrow) {
assertTrue(name + " new " + dimen + " " + newDim
+ " is less than previous " + lastDim,
newDim >= lastDim - 1);
dim = Math.max(lastDim, newDim);
} else {
assertTrue(name + " new " + dimen + " " + newDim
+ " is more than previous " + lastDim,
newDim <= lastDim + 1);
dim = Math.min(lastDim, newDim);
}
if (newDim != lastDim) {
mDidChangeSize = true;
}
}
assertTrue(name + " " + dimen + " " + newDim + " must be <= " + mMax,
newDim <= mMax);
assertTrue(name + " " + dimen + " " + newDim + " must be >= " + mMin,
newDim >= mMin);
return dim;
}
@Override
public void onDraw() {
if (mRedSquare == null) {
mRedSquare = mActivity.findViewById(R.id.redSquare);
mGreenSquare = mActivity.findViewById(R.id.greenSquare);
}
validateView(mRedSquare, mRedDimensions);
validateView(mGreenSquare, mGreenDimensions);
}
@Override
public void onAnimationStart(Animator animation) {
mActivity.getWindow().getDecorView().getViewTreeObserver().addOnDrawListener(this);
}
@Override
public void onAnimationEnd(Animator animation) {
mActivity.getWindow().getDecorView().getViewTreeObserver().removeOnDrawListener(this);
assertTrue(mDidChangeSize);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
}
}