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