• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 
17 package android.server.wm;
18 
19 import static android.graphics.Insets.NONE;
20 import static android.view.WindowInsets.Type.ime;
21 import static android.view.WindowInsets.Type.navigationBars;
22 import static android.view.WindowInsets.Type.statusBars;
23 import static android.view.WindowInsets.Type.systemBars;
24 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
25 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
26 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
27 
28 import static org.junit.Assert.assertEquals;
29 import static org.junit.Assert.assertNotNull;
30 import static org.junit.Assert.assertTrue;
31 import static org.mockito.ArgumentMatchers.any;
32 import static org.mockito.ArgumentMatchers.argThat;
33 import static org.mockito.ArgumentMatchers.eq;
34 import static org.mockito.Mockito.atLeast;
35 import static org.mockito.Mockito.inOrder;
36 import static org.mockito.Mockito.spy;
37 
38 import android.graphics.Insets;
39 import android.os.Bundle;
40 import android.os.SystemClock;
41 import android.server.wm.WindowInsetsAnimationTestBase.AnimCallback.AnimationStep;
42 import android.util.ArraySet;
43 import android.view.View;
44 import android.view.WindowInsets;
45 import android.view.WindowInsetsAnimation;
46 import android.widget.EditText;
47 import android.widget.LinearLayout;
48 import android.widget.TextView;
49 
50 import androidx.annotation.NonNull;
51 
52 import com.android.compatibility.common.util.OverrideAnimationScaleRule;
53 
54 import org.junit.Assert;
55 import org.junit.Rule;
56 import org.mockito.InOrder;
57 
58 import java.util.ArrayList;
59 import java.util.List;
60 import java.util.function.BiPredicate;
61 import java.util.function.Function;
62 import java.util.function.Predicate;
63 
64 /**
65  * Base class for tests in {@link WindowInsetsAnimation} and {@link WindowInsetsAnimation.Callback}.
66  */
67 public class WindowInsetsAnimationTestBase extends WindowManagerTestBase {
68 
69     @Rule
70     public final OverrideAnimationScaleRule mOverrideAnimationScaleRule =
71             new OverrideAnimationScaleRule(1.0f);
72 
73     protected TestActivity mActivity;
74     protected View mRootView;
75 
commonAnimationAssertions(TestActivity activity, WindowInsets before, boolean show, int types)76     protected void commonAnimationAssertions(TestActivity activity, WindowInsets before,
77             boolean show, int types) {
78 
79         AnimCallback callback = activity.mCallback;
80 
81         InOrder inOrder = inOrder(activity.mCallback, activity.mListener);
82 
83         WindowInsets after = activity.mLastWindowInsets;
84         inOrder.verify(callback).onPrepare(eq(callback.lastAnimation));
85         inOrder.verify(activity.mListener).onApplyWindowInsets(any(), any());
86 
87         inOrder.verify(callback).onStart(eq(callback.lastAnimation), argThat(
88                 argument -> argument.getLowerBound().equals(NONE)
89                         && argument.getUpperBound().equals(show
90                         ? after.getInsets(types)
91                         : before.getInsets(types))));
92 
93         inOrder.verify(callback, atLeast(2)).onProgress(any(), argThat(
94                 argument -> argument.size() == 1 && argument.get(0) == callback.lastAnimation));
95         inOrder.verify(callback).onEnd(eq(callback.lastAnimation));
96 
97         if ((types & systemBars()) != 0) {
98             assertTrue((callback.lastAnimation.getTypeMask() & systemBars()) != 0);
99         }
100         if ((types & ime()) != 0) {
101             assertTrue((callback.lastAnimation.getTypeMask() & ime()) != 0);
102         }
103         assertTrue(callback.lastAnimation.getDurationMillis() > 0);
104         assertNotNull(callback.lastAnimation.getInterpolator());
105         assertBeforeAfterState(callback.animationSteps, before, after);
106         assertAnimationSteps(callback.animationSteps, show /* increasing */);
107     }
108 
assertBeforeAfterState(ArrayList<AnimationStep> steps, WindowInsets before, WindowInsets after)109     private void assertBeforeAfterState(ArrayList<AnimationStep> steps, WindowInsets before,
110             WindowInsets after) {
111         assertEquals(before, steps.get(0).insets);
112         assertEquals(after, steps.get(steps.size() - 1).insets);
113     }
114 
hasWindowInsets(View rootView, int types)115     protected static boolean hasWindowInsets(View rootView, int types) {
116         return Insets.NONE != rootView.getRootWindowInsets().getInsetsIgnoringVisibility(types);
117     }
118 
assertAnimationSteps(ArrayList<AnimationStep> steps, boolean showAnimation)119     protected void assertAnimationSteps(ArrayList<AnimationStep> steps, boolean showAnimation) {
120         assertAnimationSteps(steps, showAnimation, systemBars());
121     }
assertAnimationSteps(ArrayList<AnimationStep> steps, boolean showAnimation, final int types)122     protected void assertAnimationSteps(ArrayList<AnimationStep> steps, boolean showAnimation,
123             final int types) {
124         assertTrue(steps.size() >= 2);
125         assertEquals(0f, steps.get(0).fraction, 0f);
126         assertEquals(0f, steps.get(0).interpolatedFraction, 0f);
127         assertEquals(1f, steps.get(steps.size() - 1).fraction, 0f);
128         assertEquals(1f, steps.get(steps.size() - 1).interpolatedFraction, 0f);
129         if (showAnimation) {
130             assertEquals(1f, steps.get(steps.size() - 1).alpha, 0f);
131         } else {
132             assertEquals(1f, steps.get(0).alpha, 0f);
133         }
134 
135         assertListElements(steps, step -> step.fraction,
136                 (current, next) -> next >= current);
137         assertListElements(steps, step -> step.interpolatedFraction,
138                 (current, next) -> next >= current);
139         assertListElements(steps, step -> step.alpha, alpha -> alpha >= 0f);
140         assertListElements(steps, step -> step.insets, compareInsets(types, showAnimation));
141     }
142 
compareInsets(int types, boolean showAnimation)143     private BiPredicate<WindowInsets, WindowInsets> compareInsets(int types,
144             boolean showAnimation) {
145         if (showAnimation) {
146             return (current, next) ->
147                     next.getInsets(types).left >= current.getInsets(types).left
148                             && next.getInsets(types).top >= current.getInsets(types).top
149                             && next.getInsets(types).right >= current.getInsets(types).right
150                             && next.getInsets(types).bottom >= current.getInsets(types).bottom;
151         } else {
152             return (current, next) ->
153                     next.getInsets(types).left <= current.getInsets(types).left
154                             && next.getInsets(types).top <= current.getInsets(types).top
155                             && next.getInsets(types).right <= current.getInsets(types).right
156                             && next.getInsets(types).bottom <= current.getInsets(types).bottom;
157         }
158     }
159 
assertListElements(ArrayList<T> list, Function<T, V> getter, Predicate<V> predicate)160     private <T, V> void assertListElements(ArrayList<T> list, Function<T, V> getter,
161             Predicate<V> predicate) {
162         for (int i = 0; i <= list.size() - 1; i++) {
163             V value = getter.apply(list.get(i));
164             assertTrue("Predicate.test failed i=" + i + " value="
165                     + value, predicate.test(value));
166         }
167     }
168 
assertListElements(ArrayList<T> list, Function<T, V> getter, BiPredicate<V, V> comparator)169     private <T, V> void assertListElements(ArrayList<T> list, Function<T, V> getter,
170             BiPredicate<V, V> comparator) {
171         for (int i = 0; i <= list.size() - 2; i++) {
172             V current = getter.apply(list.get(i));
173             V next = getter.apply(list.get(i + 1));
174             assertTrue(comparator.test(current, next));
175         }
176     }
177 
178     public static class AnimCallback extends WindowInsetsAnimation.Callback {
179 
180         public static class AnimationStep {
181 
AnimationStep(WindowInsets insets, float fraction, float interpolatedFraction, float alpha)182             AnimationStep(WindowInsets insets, float fraction, float interpolatedFraction,
183                     float alpha) {
184                 this.insets = insets;
185                 this.fraction = fraction;
186                 this.interpolatedFraction = interpolatedFraction;
187                 this.alpha = alpha;
188             }
189 
190             WindowInsets insets;
191             float fraction;
192             float interpolatedFraction;
193             float alpha;
194         }
195 
196         WindowInsetsAnimation lastAnimation;
197         volatile boolean animationDone;
198         final ArrayList<AnimationStep> animationSteps = new ArrayList<>();
199 
AnimCallback(int dispatchMode)200         public AnimCallback(int dispatchMode) {
201             super(dispatchMode);
202         }
203 
204         @Override
onPrepare(WindowInsetsAnimation animation)205         public void onPrepare(WindowInsetsAnimation animation) {
206             animationSteps.clear();
207             lastAnimation = animation;
208         }
209 
210         @Override
onStart( WindowInsetsAnimation animation, WindowInsetsAnimation.Bounds bounds)211         public WindowInsetsAnimation.Bounds onStart(
212                 WindowInsetsAnimation animation, WindowInsetsAnimation.Bounds bounds) {
213             return bounds;
214         }
215 
216         @Override
onProgress(WindowInsets insets, List<WindowInsetsAnimation> runningAnimations)217         public WindowInsets onProgress(WindowInsets insets,
218                 List<WindowInsetsAnimation> runningAnimations) {
219             animationSteps.add(new AnimationStep(insets, lastAnimation.getFraction(),
220                     lastAnimation.getInterpolatedFraction(), lastAnimation.getAlpha()));
221             return WindowInsets.CONSUMED;
222         }
223 
224         @Override
onEnd(WindowInsetsAnimation animation)225         public void onEnd(WindowInsetsAnimation animation) {
226             animationDone = true;
227         }
228     }
229 
230     protected static class MultiAnimCallback extends WindowInsetsAnimation.Callback {
231 
232         WindowInsetsAnimation statusBarAnim;
233         WindowInsetsAnimation navBarAnim;
234         WindowInsetsAnimation imeAnim;
235         volatile boolean imeAnimStarted;
236         volatile boolean animationDone;
237         final ArrayList<AnimationStep> statusAnimSteps = new ArrayList<>();
238         final ArrayList<AnimationStep> navAnimSteps = new ArrayList<>();
239         final ArrayList<AnimationStep> imeAnimSteps = new ArrayList<>();
240         Runnable startRunnable;
241         final ArraySet<WindowInsetsAnimation> runningAnims = new ArraySet<>();
242 
MultiAnimCallback()243         public MultiAnimCallback() {
244             super(DISPATCH_MODE_STOP);
245         }
246 
247         @Override
onPrepare(WindowInsetsAnimation animation)248         public void onPrepare(WindowInsetsAnimation animation) {
249             if ((animation.getTypeMask() & statusBars()) != 0) {
250                 statusBarAnim = animation;
251             }
252             if ((animation.getTypeMask() & navigationBars()) != 0) {
253                 navBarAnim = animation;
254             }
255             if ((animation.getTypeMask() & ime()) != 0) {
256                 imeAnim = animation;
257             }
258         }
259 
260         @Override
onStart( WindowInsetsAnimation animation, WindowInsetsAnimation.Bounds bounds)261         public WindowInsetsAnimation.Bounds onStart(
262                 WindowInsetsAnimation animation, WindowInsetsAnimation.Bounds bounds) {
263             if (startRunnable != null) {
264                 startRunnable.run();
265             }
266             runningAnims.add(animation);
267             if (animation.equals(imeAnim)) {
268                 imeAnimStarted = true;
269             }
270             return bounds;
271         }
272 
273         @Override
onProgress(WindowInsets insets, List<WindowInsetsAnimation> runningAnimations)274         public WindowInsets onProgress(WindowInsets insets,
275                 List<WindowInsetsAnimation> runningAnimations) {
276             if (statusBarAnim != null) {
277                 statusAnimSteps.add(new AnimationStep(insets, statusBarAnim.getFraction(),
278                         statusBarAnim.getInterpolatedFraction(), statusBarAnim.getAlpha()));
279             }
280             if (navBarAnim != null) {
281                 navAnimSteps.add(new AnimationStep(insets, navBarAnim.getFraction(),
282                         navBarAnim.getInterpolatedFraction(), navBarAnim.getAlpha()));
283             }
284             if (imeAnim != null) {
285                 imeAnimSteps.add(new AnimationStep(insets, imeAnim.getFraction(),
286                         imeAnim.getInterpolatedFraction(), imeAnim.getAlpha()));
287             }
288 
289             assertEquals(runningAnims.size(), runningAnimations.size());
290             for (int i = runningAnimations.size() - 1; i >= 0; i--) {
291                 Assert.assertNotEquals(-1,
292                         runningAnims.indexOf(runningAnimations.get(i)));
293             }
294 
295             return WindowInsets.CONSUMED;
296         }
297 
298         @Override
onEnd(WindowInsetsAnimation animation)299         public void onEnd(WindowInsetsAnimation animation) {
300             runningAnims.remove(animation);
301             if (runningAnims.isEmpty()) {
302                 animationDone = true;
303             }
304         }
305     }
306 
307     public static class TestActivity extends FocusableActivity {
308 
309         private final String mEditTextMarker =
310                 "android.server.wm.WindowInsetsAnimationTestBase.TestActivity"
311                         + SystemClock.elapsedRealtimeNanos();
312 
313         AnimCallback mCallback =
314                 spy(new AnimCallback(WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP));
315         WindowInsets mLastWindowInsets;
316         /**
317          * Save the WindowInsets when animation done. Acoid to mLastWindowInsets
318          * always be updated after windowinsets animation done on low-ram devices.
319          */
320         WindowInsets mLastPendingWindowInsets;
321 
322         View.OnApplyWindowInsetsListener mListener;
323         LinearLayout mView;
324         View mChild;
325         EditText mEditor;
326 
327         public class InsetsListener implements View.OnApplyWindowInsetsListener {
328 
329             @Override
onApplyWindowInsets(View v, WindowInsets insets)330             public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
331                 /**
332                  * Do not update mLastWindowInsets and save the latest WindowInsets to
333                  *  mLastPendingWindowInsets.
334                  */
335                 if (mCallback.animationDone) {
336                     mLastPendingWindowInsets = insets;
337                     return WindowInsets.CONSUMED;
338                 }
339                 mLastWindowInsets = insets;
340                 mLastPendingWindowInsets = null;
341                 return WindowInsets.CONSUMED;
342             }
343         }
344 
345         @NonNull
getEditTextMarker()346         String getEditTextMarker() {
347             return mEditTextMarker;
348         }
349 
350         @Override
onCreate(Bundle savedInstanceState)351         protected void onCreate(Bundle savedInstanceState) {
352             super.onCreate(savedInstanceState);
353             mListener = spy(new InsetsListener());
354             mView = new LinearLayout(this);
355             mView.setWindowInsetsAnimationCallback(mCallback);
356             mView.setOnApplyWindowInsetsListener(mListener);
357             mChild = new TextView(this);
358             mEditor = new EditText(this);
359             mEditor.setPrivateImeOptions(mEditTextMarker);
360             mView.addView(mChild);
361             mView.addView(mEditor);
362 
363             setContentView(mView);
364 
365             getWindow().setDecorFitsSystemWindows(false);
366             getWindow().getAttributes().layoutInDisplayCutoutMode =
367                     LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
368             getWindow().setSoftInputMode(SOFT_INPUT_STATE_HIDDEN);
369             getWindow().getDecorView().getWindowInsetsController().setSystemBarsBehavior(
370                     BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
371             mEditor.requestFocus();
372         }
373 
resetAnimationDone()374         public void resetAnimationDone() {
375             mCallback.animationDone = false;
376             /**
377              * Do not update mLastWindowInsets and save the latest WindowInsets to
378              *  mLastPendingWindowInsets.
379              */
380             if (mLastPendingWindowInsets != null) {
381                 mLastWindowInsets = new WindowInsets(mLastPendingWindowInsets);
382                 mLastPendingWindowInsets = null;
383             }
384         }
385     }
386 }
387