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