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