BoundsAnimationControllerTests.java revision ecc06b32305ac234db24e3f76bcae0199b80c395
1/*
2 * Copyright (C) 2017 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
17package com.android.server.wm;
18
19import static com.android.server.wm.BoundsAnimationController.NO_PIP_MODE_CHANGED_CALLBACKS;
20import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_END;
21import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_START;
22import static com.android.server.wm.BoundsAnimationController.SchedulePipModeChangedState;
23
24import android.animation.ValueAnimator;
25import android.content.Context;
26import android.graphics.Rect;
27import android.os.Handler;
28import android.os.Looper;
29import android.platform.test.annotations.Presubmit;
30import android.support.test.InstrumentationRegistry;
31import android.support.test.annotation.UiThreadTest;
32import android.support.test.filters.SmallTest;
33import android.support.test.runner.AndroidJUnit4;
34import android.view.WindowManagerInternal.AppTransitionListener;
35
36import org.junit.Test;
37import org.junit.runner.RunWith;
38
39import static org.junit.Assert.assertEquals;
40import static org.junit.Assert.assertFalse;
41import static org.junit.Assert.assertNotEquals;
42import static org.junit.Assert.assertNotNull;
43import static org.junit.Assert.assertNotSame;
44import static org.junit.Assert.assertNull;
45import static org.junit.Assert.assertSame;
46import static org.junit.Assert.assertTrue;
47import static org.junit.Assert.fail;
48
49import com.android.server.wm.BoundsAnimationController.BoundsAnimator;
50
51/**
52 * Test class for {@link BoundsAnimationController} to ensure that it sends the right callbacks
53 * depending on the various interactions.
54 *
55 * We are really concerned about only three of the transition states [F = fullscreen, !F = floating]
56 * F->!F, !F->!F, and !F->F. Each animation can only be cancelled from the target mid-transition,
57 * or if a new animation starts on the same target.  The tests below verifies that the target is
58 * notified of all the cases where it is animating and cancelled so that it can respond
59 * appropriately.
60 *
61 * Build/Install/Run:
62 *  bit FrameworksServicesTests:com.android.server.wm.BoundsAnimationControllerTests
63 */
64@SmallTest
65@Presubmit
66@RunWith(AndroidJUnit4.class)
67public class BoundsAnimationControllerTests extends WindowTestsBase {
68
69    /**
70     * Mock value animator to simulate updates with.
71     */
72    private class MockValueAnimator extends ValueAnimator {
73
74        private float mFraction;
75
76        public MockValueAnimator getWithValue(float fraction) {
77            mFraction = fraction;
78            return this;
79        }
80
81        @Override
82        public Object getAnimatedValue() {
83            return mFraction;
84        }
85    }
86
87    /**
88     * Mock app transition to fire notifications to the bounds animator.
89     */
90    private class MockAppTransition extends AppTransition {
91
92        private AppTransitionListener mListener;
93
94        MockAppTransition(Context context) {
95            super(context, null);
96        }
97
98        @Override
99        void registerListenerLocked(AppTransitionListener listener) {
100            mListener = listener;
101        }
102
103        public void notifyTransitionPending() {
104            mListener.onAppTransitionPendingLocked();
105        }
106
107        public void notifyTransitionCancelled(int transit) {
108            mListener.onAppTransitionCancelledLocked(transit);
109        }
110
111        public void notifyTransitionStarting(int transit) {
112            mListener.onAppTransitionStartingLocked(transit, null, null, null, null);
113        }
114
115        public void notifyTransitionFinished() {
116            mListener.onAppTransitionFinishedLocked(null);
117        }
118    }
119
120    /**
121     * A test animate bounds user to track callbacks from the bounds animation.
122     */
123    private class TestBoundsAnimationTarget implements BoundsAnimationTarget {
124
125        boolean mAwaitingAnimationStart;
126        boolean mMovedToFullscreen;
127        boolean mAnimationStarted;
128        boolean mSchedulePipModeChangedOnStart;
129        boolean mAnimationEnded;
130        Rect mAnimationEndFinalStackBounds;
131        boolean mSchedulePipModeChangedOnEnd;
132        boolean mBoundsUpdated;
133        boolean mCancelRequested;
134        Rect mStackBounds;
135        Rect mTaskBounds;
136
137        void initialize(Rect from) {
138            mAwaitingAnimationStart = true;
139            mMovedToFullscreen = false;
140            mAnimationStarted = false;
141            mAnimationEnded = false;
142            mAnimationEndFinalStackBounds = null;
143            mSchedulePipModeChangedOnStart = false;
144            mSchedulePipModeChangedOnEnd = false;
145            mStackBounds = from;
146            mTaskBounds = null;
147            mBoundsUpdated = false;
148        }
149
150        @Override
151        public void onAnimationStart(boolean schedulePipModeChangedCallback) {
152            mAwaitingAnimationStart = false;
153            mAnimationStarted = true;
154            mSchedulePipModeChangedOnStart = schedulePipModeChangedCallback;
155
156            mController.onAllWindowsDrawn();
157        }
158
159        @Override
160        public boolean setPinnedStackSize(Rect stackBounds, Rect taskBounds) {
161            // TODO: Once we break the runs apart, we should fail() here if this is called outside
162            //       of onAnimationStart() and onAnimationEnd()
163            if (mCancelRequested) {
164                mCancelRequested = false;
165                return false;
166            } else {
167                mBoundsUpdated = true;
168                mStackBounds = stackBounds;
169                mTaskBounds = taskBounds;
170                return true;
171            }
172        }
173
174        @Override
175        public void onAnimationEnd(boolean schedulePipModeChangedCallback, Rect finalStackBounds,
176                boolean moveToFullscreen) {
177            mAnimationEnded = true;
178            mAnimationEndFinalStackBounds = finalStackBounds;
179            mSchedulePipModeChangedOnEnd = schedulePipModeChangedCallback;
180            mMovedToFullscreen = moveToFullscreen;
181            mTaskBounds = null;
182        }
183    }
184
185    /**
186     * Drives the animations, makes common assertions along the way.
187     */
188    private class BoundsAnimationDriver {
189
190        private BoundsAnimationController mController;
191        private TestBoundsAnimationTarget mTarget;
192        private BoundsAnimator mAnimator;
193
194        private Rect mFrom;
195        private Rect mTo;
196        private Rect mLargerBounds;
197        private Rect mExpectedFinalBounds;
198
199        BoundsAnimationDriver(BoundsAnimationController controller,
200                TestBoundsAnimationTarget target) {
201            mController = controller;
202            mTarget = target;
203        }
204
205        BoundsAnimationDriver start(Rect from, Rect to) {
206            if (mAnimator != null) {
207                throw new IllegalArgumentException("Call restart() to restart an animation");
208            }
209
210            mTarget.initialize(from);
211
212            // Started, not running
213            assertTrue(mTarget.mAwaitingAnimationStart);
214            assertTrue(!mTarget.mAnimationStarted);
215
216            startImpl(from, to);
217
218            // Started and running
219            assertTrue(!mTarget.mAwaitingAnimationStart);
220            assertTrue(mTarget.mAnimationStarted);
221
222            return this;
223        }
224
225        BoundsAnimationDriver restart(Rect to) {
226            if (mAnimator == null) {
227                throw new IllegalArgumentException("Call start() to start a new animation");
228            }
229
230            BoundsAnimator oldAnimator = mAnimator;
231            boolean toSameBounds = mAnimator.isStarted() && to.equals(mTo);
232
233            // Reset the animation start state
234            mTarget.mAnimationStarted = false;
235
236            // Start animation
237            startImpl(mTarget.mStackBounds, to);
238
239            if (toSameBounds) {
240                // Same animator if same final bounds
241                assertSame(oldAnimator, mAnimator);
242            }
243
244            // No animation start for replacing animation
245            assertTrue(!mTarget.mAnimationStarted);
246            mTarget.mAnimationStarted = true;
247            return this;
248        }
249
250        private BoundsAnimationDriver startImpl(Rect from, Rect to) {
251            boolean fromFullscreen = from.equals(BOUNDS_FULL);
252            boolean toFullscreen = to.equals(BOUNDS_FULL);
253            mFrom = new Rect(from);
254            mTo = new Rect(to);
255            mExpectedFinalBounds = new Rect(to);
256            mLargerBounds = getLargerBounds(mFrom, mTo);
257
258            // Start animation
259            final @SchedulePipModeChangedState int schedulePipModeChangedState = toFullscreen
260                    ? SCHEDULE_PIP_MODE_CHANGED_ON_START
261                    : fromFullscreen
262                            ? SCHEDULE_PIP_MODE_CHANGED_ON_END
263                            : NO_PIP_MODE_CHANGED_CALLBACKS;
264            mAnimator = mController.animateBoundsImpl(mTarget, from, to, DURATION,
265                    schedulePipModeChangedState, toFullscreen);
266
267            // Original stack bounds, frozen task bounds
268            assertEquals(mFrom, mTarget.mStackBounds);
269            assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds);
270
271            // Animating to larger size
272            if (mFrom.equals(mLargerBounds)) {
273                assertTrue(!mAnimator.animatingToLargerSize());
274            } else if (mTo.equals(mLargerBounds)) {
275                assertTrue(mAnimator.animatingToLargerSize());
276            }
277
278            return this;
279        }
280
281        BoundsAnimationDriver expectStarted(boolean schedulePipModeChanged) {
282            // Callback made
283            assertTrue(mTarget.mAnimationStarted);
284
285            assertEquals(schedulePipModeChanged, mTarget.mSchedulePipModeChangedOnStart);
286            return this;
287        }
288
289        BoundsAnimationDriver update(float t) {
290            mAnimator.onAnimationUpdate(mMockAnimator.getWithValue(t));
291
292            // Temporary stack bounds, frozen task bounds
293            if (t == 0f) {
294                assertEquals(mFrom, mTarget.mStackBounds);
295            } else if (t == 1f) {
296                assertEquals(mTo, mTarget.mStackBounds);
297            } else {
298                assertNotEquals(mFrom, mTarget.mStackBounds);
299                assertNotEquals(mTo, mTarget.mStackBounds);
300            }
301            assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds);
302            return this;
303        }
304
305        BoundsAnimationDriver cancel() {
306            // Cancel
307            mTarget.mCancelRequested = true;
308            mTarget.mBoundsUpdated = false;
309            mExpectedFinalBounds = null;
310
311            // Update
312            mAnimator.onAnimationUpdate(mMockAnimator.getWithValue(0.5f));
313
314            // Not started, not running, cancel reset
315            assertTrue(!mTarget.mCancelRequested);
316
317            // Stack/task bounds not updated
318            assertTrue(!mTarget.mBoundsUpdated);
319
320            // Callback made
321            assertTrue(mTarget.mAnimationEnded);
322            assertNull(mTarget.mAnimationEndFinalStackBounds);
323
324            return this;
325        }
326
327        BoundsAnimationDriver end() {
328            mAnimator.end();
329
330            // Final stack bounds
331            assertEquals(mTo, mTarget.mStackBounds);
332            assertEquals(mExpectedFinalBounds, mTarget.mAnimationEndFinalStackBounds);
333            assertNull(mTarget.mTaskBounds);
334
335            return this;
336        }
337
338        BoundsAnimationDriver expectEnded(boolean schedulePipModeChanged,
339                boolean moveToFullscreen) {
340            // Callback made
341            assertTrue(mTarget.mAnimationEnded);
342
343            assertEquals(schedulePipModeChanged, mTarget.mSchedulePipModeChangedOnEnd);
344            assertEquals(moveToFullscreen, mTarget.mMovedToFullscreen);
345            return this;
346        }
347
348        private Rect getLargerBounds(Rect r1, Rect r2) {
349            int r1Area = r1.width() * r1.height();
350            int r2Area = r2.width() * r2.height();
351            if (r1Area <= r2Area) {
352                return r2;
353            } else {
354                return r1;
355            }
356        }
357    }
358
359    // Constants
360    private static final boolean SCHEDULE_PIP_MODE_CHANGED = true;
361    private static final boolean MOVE_TO_FULLSCREEN = true;
362    private static final int DURATION = 100;
363
364    // Some dummy bounds to represent fullscreen and floating bounds
365    private static final Rect BOUNDS_FULL = new Rect(0, 0, 100, 100);
366    private static final Rect BOUNDS_FLOATING = new Rect(60, 60, 95, 95);
367    private static final Rect BOUNDS_SMALLER_FLOATING = new Rect(80, 80, 95, 95);
368
369    // Common
370    private MockAppTransition mMockAppTransition;
371    private MockValueAnimator mMockAnimator;
372    private TestBoundsAnimationTarget mTarget;
373    private BoundsAnimationController mController;
374    private BoundsAnimationDriver mDriver;
375
376    // Temp
377    private Rect mTmpRect = new Rect();
378
379    @Override
380    public void setUp() throws Exception {
381        super.setUp();
382
383        final Context context = InstrumentationRegistry.getTargetContext();
384        final Handler handler = new Handler(Looper.getMainLooper());
385        mMockAppTransition = new MockAppTransition(context);
386        mMockAnimator = new MockValueAnimator();
387        mTarget = new TestBoundsAnimationTarget();
388        mController = new BoundsAnimationController(context, mMockAppTransition, handler);
389        mDriver = new BoundsAnimationDriver(mController, mTarget);
390    }
391
392    /** BASE TRANSITIONS **/
393
394    @UiThreadTest
395    @Test
396    public void testFullscreenToFloatingTransition() throws Exception {
397        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
398                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
399                .update(0f)
400                .update(0.5f)
401                .update(1f)
402                .end()
403                .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
404    }
405
406    @UiThreadTest
407    @Test
408    public void testFloatingToFullscreenTransition() throws Exception {
409        mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
410                .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
411                .update(0f)
412                .update(0.5f)
413                .update(1f)
414                .end()
415                .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN);
416    }
417
418    @UiThreadTest
419    @Test
420    public void testFloatingToSmallerFloatingTransition() throws Exception {
421        mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING)
422                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
423                .update(0f)
424                .update(0.5f)
425                .update(1f)
426                .end()
427                .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
428    }
429
430    @UiThreadTest
431    @Test
432    public void testFloatingToLargerFloatingTransition() throws Exception {
433        mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING)
434                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
435                .update(0f)
436                .update(0.5f)
437                .update(1f)
438                .end()
439                .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
440    }
441
442    /** F->!F w/ CANCEL **/
443
444    @UiThreadTest
445    @Test
446    public void testFullscreenToFloatingCancelFromTarget() throws Exception {
447        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
448                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
449                .update(0.25f)
450                .cancel()
451                .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
452    }
453
454    @UiThreadTest
455    @Test
456    public void testFullscreenToFloatingCancelFromAnimationToSameBounds() throws Exception {
457        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
458                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
459                .update(0.25f)
460                .restart(BOUNDS_FLOATING)
461                .end()
462                .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
463    }
464
465    @UiThreadTest
466    @Test
467    public void testFullscreenToFloatingCancelFromAnimationToFloatingBounds() throws Exception {
468        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
469                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
470                .update(0.25f)
471                .restart(BOUNDS_SMALLER_FLOATING)
472                .end()
473                .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
474    }
475
476    @UiThreadTest
477    @Test
478    public void testFullscreenToFloatingCancelFromAnimationToFullscreenBounds() throws Exception {
479        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
480                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
481                .update(0.25f)
482                .restart(BOUNDS_FULL)
483                .end()
484                .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN);
485    }
486
487    /** !F->F w/ CANCEL **/
488
489    @UiThreadTest
490    @Test
491    public void testFloatingToFullscreenCancelFromTarget() throws Exception {
492        mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
493                .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
494                .update(0.25f)
495                .cancel()
496                .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
497    }
498
499    @UiThreadTest
500    @Test
501    public void testFloatingToFullscreenCancelFromAnimationToSameBounds() throws Exception {
502        mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
503                .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
504                .update(0.25f)
505                .restart(BOUNDS_FULL)
506                .end()
507                .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN);
508    }
509
510    @UiThreadTest
511    @Test
512    public void testFloatingToFullscreenCancelFromAnimationToFloatingBounds() throws Exception {
513        mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
514                .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
515                .update(0.25f)
516                .restart(BOUNDS_SMALLER_FLOATING)
517                .end()
518                .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
519    }
520
521    /** !F->!F w/ CANCEL **/
522
523    @UiThreadTest
524    @Test
525    public void testFloatingToSmallerFloatingCancelFromTarget() throws Exception {
526        mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING)
527                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
528                .update(0.25f)
529                .cancel()
530                .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
531    }
532
533    @UiThreadTest
534    @Test
535    public void testFloatingToLargerFloatingCancelFromTarget() throws Exception {
536        mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING)
537                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
538                .update(0.25f)
539                .cancel()
540                .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
541    }
542
543    /** MISC **/
544
545    @UiThreadTest
546    @Test
547    public void testBoundsAreCopied() throws Exception {
548        Rect from = new Rect(0, 0, 100, 100);
549        Rect to = new Rect(25, 25, 75, 75);
550        mDriver.start(from, to)
551                .update(0.25f)
552                .end();
553        assertEquals(new Rect(0, 0, 100, 100), from);
554        assertEquals(new Rect(25, 25, 75, 75), to);
555    }
556
557    /**
558     * @return whether the task and stack bounds would be the same if they were at the same offset.
559     */
560    private boolean assertEqualSizeAtOffset(Rect stackBounds, Rect taskBounds) {
561        mTmpRect.set(taskBounds);
562        mTmpRect.offsetTo(stackBounds.left, stackBounds.top);
563        return stackBounds.equals(mTmpRect);
564    }
565}
566