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 package android.transition.cts; 17 18 import static com.android.compatibility.common.util.CtsMockitoUtils.within; 19 20 import static org.mockito.Matchers.any; 21 import static org.mockito.Mockito.mock; 22 import static org.mockito.Mockito.timeout; 23 import static org.mockito.Mockito.times; 24 import static org.mockito.Mockito.verify; 25 26 import android.animation.Animator; 27 import android.animation.ObjectAnimator; 28 import android.app.Instrumentation; 29 import android.graphics.PointF; 30 import android.transition.Scene; 31 import android.transition.Transition; 32 import android.transition.TransitionManager; 33 import android.transition.TransitionValues; 34 import android.transition.Visibility; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import android.view.ViewTreeObserver; 38 import android.widget.FrameLayout; 39 40 import androidx.test.InstrumentationRegistry; 41 import androidx.test.rule.ActivityTestRule; 42 43 import com.android.compatibility.common.util.WidgetTestUtils; 44 45 import org.junit.Before; 46 import org.junit.Rule; 47 import org.mockito.Mockito; 48 49 import java.util.ArrayList; 50 import java.util.List; 51 52 public abstract class BaseTransitionTest { 53 protected Instrumentation mInstrumentation; 54 protected TransitionActivity mActivity; 55 protected FrameLayout mSceneRoot; 56 private float mAnimatedValue; 57 protected ArrayList<View> mTargets = new ArrayList<>(); 58 protected Transition mTransition; 59 protected Transition.TransitionListener mListener; 60 61 @Rule 62 public ActivityTestRule<TransitionActivity> mActivityRule = 63 new ActivityTestRule<>(TransitionActivity.class); 64 65 @Before setup()66 public void setup() { 67 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 68 mInstrumentation.setInTouchMode(false); 69 mActivity = mActivityRule.getActivity(); 70 mSceneRoot = (FrameLayout) mActivity.findViewById(R.id.container); 71 mTargets.clear(); 72 mTransition = new TestTransition(); 73 mListener = mock(Transition.TransitionListener.class); 74 mTransition.addListener(mListener); 75 } 76 waitForStart()77 protected void waitForStart() throws InterruptedException { 78 waitForStart(mListener); 79 } 80 waitForStart(Transition.TransitionListener listener)81 protected static void waitForStart(Transition.TransitionListener listener) { 82 verify(listener, within(4000)).onTransitionStart(any()); 83 } 84 waitForEnd(long waitMillis)85 protected void waitForEnd(long waitMillis) { 86 waitForEnd(mListener, waitMillis); 87 mInstrumentation.waitForIdleSync(); 88 } 89 waitForEnd(Transition.TransitionListener listener, long waitMillis)90 protected static void waitForEnd(Transition.TransitionListener listener, long waitMillis) { 91 if (waitMillis == 0) { 92 verify(listener, times(1)).onTransitionEnd(any()); 93 } else { 94 verify(listener, within(waitMillis)).onTransitionEnd(any()); 95 } 96 } 97 loadLayout(final int layout)98 protected View loadLayout(final int layout) throws Throwable { 99 View[] root = new View[1]; 100 101 mActivityRule.runOnUiThread( 102 () -> root[0] = mActivity.getLayoutInflater().inflate(layout, mSceneRoot, false)); 103 104 return root[0]; 105 } 106 loadScene(final View layout)107 protected Scene loadScene(final View layout) throws Throwable { 108 final Scene[] scene = new Scene[1]; 109 mActivityRule.runOnUiThread(() -> scene[0] = new Scene(mSceneRoot, layout)); 110 111 return scene[0]; 112 } 113 loadScene(final int layoutId)114 protected Scene loadScene(final int layoutId) throws Throwable { 115 final Scene scene[] = new Scene[1]; 116 mActivityRule.runOnUiThread( 117 () -> scene[0] = Scene.getSceneForLayout(mSceneRoot, layoutId, mActivity)); 118 return scene[0]; 119 } 120 startTransition(final int layoutId)121 protected void startTransition(final int layoutId) throws Throwable { 122 startTransition(loadScene(layoutId)); 123 } 124 startTransition(final Scene scene)125 protected void startTransition(final Scene scene) throws Throwable { 126 mActivityRule.runOnUiThread(() -> TransitionManager.go(scene, mTransition)); 127 waitForStart(); 128 } 129 endTransition()130 protected void endTransition() throws Throwable { 131 mActivityRule.runOnUiThread(() -> TransitionManager.endTransitions(mSceneRoot)); 132 } 133 enterScene(final int layoutId)134 protected void enterScene(final int layoutId) throws Throwable { 135 enterScene(loadScene(layoutId)); 136 } 137 enterScene(final Scene scene)138 protected void enterScene(final Scene scene) throws Throwable { 139 WidgetTestUtils.runOnMainAndLayoutSync(mActivityRule, scene::enter, false); 140 } 141 exitScene(final Scene scene)142 protected void exitScene(final Scene scene) throws Throwable { 143 mActivityRule.runOnUiThread(scene::exit); 144 mInstrumentation.waitForIdleSync(); 145 } 146 resetListener()147 protected void resetListener() { 148 mTransition.removeListener(mListener); 149 mListener = mock(Transition.TransitionListener.class); 150 mTransition.addListener(mListener); 151 } 152 setAnimatedValue(float animatedValue)153 public void setAnimatedValue(float animatedValue) { 154 mAnimatedValue = animatedValue; 155 } 156 captureTranslations(View view)157 List<PointF> captureTranslations(View view) throws Throwable { 158 final ArrayList<PointF> points = Mockito.spy(new ArrayList<>()); 159 mActivityRule.runOnUiThread(() -> { 160 ViewTreeObserver.OnDrawListener listener = new ViewTreeObserver.OnDrawListener() { 161 @Override 162 public void onDraw() { 163 float x = view.getTranslationX(); 164 float y = view.getTranslationY(); 165 if (points.isEmpty() || !points.get(points.size() - 1).equals(x, y)) { 166 points.add(new PointF(x, y)); 167 } 168 if (points.size() > 3 && x == 0f && y == 0f) { 169 view.post(() -> { 170 view.getViewTreeObserver().removeOnDrawListener(this); 171 }); 172 } 173 } 174 }; 175 view.getViewTreeObserver().addOnDrawListener(listener); 176 view.invalidate(); 177 }); 178 verify(points, timeout(1000).times(1)).add(any()); 179 return points; 180 } 181 182 183 public class TestTransition extends Visibility { 184 TestTransition()185 public TestTransition() { 186 } 187 188 @Override onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues)189 public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, 190 TransitionValues endValues) { 191 mTargets.add(endValues.view); 192 return ObjectAnimator.ofFloat(BaseTransitionTest.this, "animatedValue", 0, 1); 193 } 194 195 @Override onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues)196 public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, 197 TransitionValues endValues) { 198 mTargets.add(startValues.view); 199 return ObjectAnimator.ofFloat(BaseTransitionTest.this, "animatedValue", 1, 0); 200 } 201 } 202 } 203