BoundsAnimationControllerTests.java revision 4a526c124554e75dc4bc11a682645a73bd47d501
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
157        @Override
158        public boolean setPinnedStackSize(Rect stackBounds, Rect taskBounds) {
159            // TODO: Once we break the runs apart, we should fail() here if this is called outside
160            //       of onAnimationStart() and onAnimationEnd()
161            if (mCancelRequested) {
162                mCancelRequested = false;
163                return false;
164            } else {
165                mBoundsUpdated = true;
166                mStackBounds = stackBounds;
167                mTaskBounds = taskBounds;
168                return true;
169            }
170        }
171
172        @Override
173        public void onAnimationEnd(boolean schedulePipModeChangedCallback, Rect finalStackBounds,
174                boolean moveToFullscreen) {
175            mAnimationEnded = true;
176            mAnimationEndFinalStackBounds = finalStackBounds;
177            mSchedulePipModeChangedOnEnd = schedulePipModeChangedCallback;
178            mMovedToFullscreen = moveToFullscreen;
179            mTaskBounds = null;
180        }
181    }
182
183    /**
184     * Drives the animations, makes common assertions along the way.
185     */
186    private class BoundsAnimationDriver {
187
188        private BoundsAnimationController mController;
189        private TestBoundsAnimationTarget mTarget;
190        private BoundsAnimator mAnimator;
191
192        private Rect mFrom;
193        private Rect mTo;
194        private Rect mLargerBounds;
195        private Rect mExpectedFinalBounds;
196
197        BoundsAnimationDriver(BoundsAnimationController controller,
198                TestBoundsAnimationTarget target) {
199            mController = controller;
200            mTarget = target;
201        }
202
203        BoundsAnimationDriver start(Rect from, Rect to) {
204            if (mAnimator != null) {
205                throw new IllegalArgumentException("Call restart() to restart an animation");
206            }
207
208            boolean fromFullscreen = from.equals(BOUNDS_FULL);
209            boolean toFullscreen = to.equals(BOUNDS_FULL);
210
211            mTarget.initialize(from);
212
213            // Started, not running
214            assertTrue(mTarget.mAwaitingAnimationStart);
215            assertTrue(!mTarget.mAnimationStarted);
216
217            startImpl(from, to);
218
219            // Ensure that the animator is paused for the all windows drawn signal when animating
220            // to/from fullscreen
221            if (fromFullscreen || toFullscreen) {
222                assertTrue(mAnimator.isPaused());
223                mController.onAllWindowsDrawn();
224            } else {
225                assertTrue(!mAnimator.isPaused());
226            }
227
228            // Started and running
229            assertTrue(!mTarget.mAwaitingAnimationStart);
230            assertTrue(mTarget.mAnimationStarted);
231
232            return this;
233        }
234
235        BoundsAnimationDriver restart(Rect to) {
236            if (mAnimator == null) {
237                throw new IllegalArgumentException("Call start() to start a new animation");
238            }
239
240            BoundsAnimator oldAnimator = mAnimator;
241            boolean toSameBounds = mAnimator.isStarted() && to.equals(mTo);
242
243            // Reset the animation start state
244            mTarget.mAnimationStarted = false;
245
246            // Start animation
247            startImpl(mTarget.mStackBounds, to);
248
249            if (toSameBounds) {
250                // Same animator if same final bounds
251                assertSame(oldAnimator, mAnimator);
252            }
253
254            // No animation start for replacing animation
255            assertTrue(!mTarget.mAnimationStarted);
256            mTarget.mAnimationStarted = true;
257            return this;
258        }
259
260        private BoundsAnimationDriver startImpl(Rect from, Rect to) {
261            boolean fromFullscreen = from.equals(BOUNDS_FULL);
262            boolean toFullscreen = to.equals(BOUNDS_FULL);
263            mFrom = new Rect(from);
264            mTo = new Rect(to);
265            mExpectedFinalBounds = new Rect(to);
266            mLargerBounds = getLargerBounds(mFrom, mTo);
267
268            // Start animation
269            final @SchedulePipModeChangedState int schedulePipModeChangedState = toFullscreen
270                    ? SCHEDULE_PIP_MODE_CHANGED_ON_START
271                    : fromFullscreen
272                            ? SCHEDULE_PIP_MODE_CHANGED_ON_END
273                            : NO_PIP_MODE_CHANGED_CALLBACKS;
274            mAnimator = mController.animateBoundsImpl(mTarget, from, to, DURATION,
275                    schedulePipModeChangedState, fromFullscreen, toFullscreen);
276
277            // Original stack bounds, frozen task bounds
278            assertEquals(mFrom, mTarget.mStackBounds);
279            assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds);
280
281            // Animating to larger size
282            if (mFrom.equals(mLargerBounds)) {
283                assertTrue(!mAnimator.animatingToLargerSize());
284            } else if (mTo.equals(mLargerBounds)) {
285                assertTrue(mAnimator.animatingToLargerSize());
286            }
287
288            return this;
289        }
290
291        BoundsAnimationDriver expectStarted(boolean schedulePipModeChanged) {
292            // Callback made
293            assertTrue(mTarget.mAnimationStarted);
294
295            assertEquals(schedulePipModeChanged, mTarget.mSchedulePipModeChangedOnStart);
296            return this;
297        }
298
299        BoundsAnimationDriver update(float t) {
300            mAnimator.onAnimationUpdate(mMockAnimator.getWithValue(t));
301
302            // Temporary stack bounds, frozen task bounds
303            if (t == 0f) {
304                assertEquals(mFrom, mTarget.mStackBounds);
305            } else if (t == 1f) {
306                assertEquals(mTo, mTarget.mStackBounds);
307            } else {
308                assertNotEquals(mFrom, mTarget.mStackBounds);
309                assertNotEquals(mTo, mTarget.mStackBounds);
310            }
311            assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds);
312            return this;
313        }
314
315        BoundsAnimationDriver cancel() {
316            // Cancel
317            mTarget.mCancelRequested = true;
318            mTarget.mBoundsUpdated = false;
319            mExpectedFinalBounds = null;
320
321            // Update
322            mAnimator.onAnimationUpdate(mMockAnimator.getWithValue(0.5f));
323
324            // Not started, not running, cancel reset
325            assertTrue(!mTarget.mCancelRequested);
326
327            // Stack/task bounds not updated
328            assertTrue(!mTarget.mBoundsUpdated);
329
330            // Callback made
331            assertTrue(mTarget.mAnimationEnded);
332            assertNull(mTarget.mAnimationEndFinalStackBounds);
333
334            return this;
335        }
336
337        BoundsAnimationDriver end() {
338            mAnimator.end();
339
340            // Final stack bounds
341            assertEquals(mTo, mTarget.mStackBounds);
342            assertEquals(mExpectedFinalBounds, mTarget.mAnimationEndFinalStackBounds);
343            assertNull(mTarget.mTaskBounds);
344
345            return this;
346        }
347
348        BoundsAnimationDriver expectEnded(boolean schedulePipModeChanged,
349                boolean moveToFullscreen) {
350            // Callback made
351            assertTrue(mTarget.mAnimationEnded);
352
353            assertEquals(schedulePipModeChanged, mTarget.mSchedulePipModeChangedOnEnd);
354            assertEquals(moveToFullscreen, mTarget.mMovedToFullscreen);
355            return this;
356        }
357
358        private Rect getLargerBounds(Rect r1, Rect r2) {
359            int r1Area = r1.width() * r1.height();
360            int r2Area = r2.width() * r2.height();
361            if (r1Area <= r2Area) {
362                return r2;
363            } else {
364                return r1;
365            }
366        }
367    }
368
369    // Constants
370    private static final boolean SCHEDULE_PIP_MODE_CHANGED = true;
371    private static final boolean MOVE_TO_FULLSCREEN = true;
372    private static final int DURATION = 100;
373
374    // Some dummy bounds to represent fullscreen and floating bounds
375    private static final Rect BOUNDS_FULL = new Rect(0, 0, 100, 100);
376    private static final Rect BOUNDS_FLOATING = new Rect(60, 60, 95, 95);
377    private static final Rect BOUNDS_SMALLER_FLOATING = new Rect(80, 80, 95, 95);
378
379    // Common
380    private MockAppTransition mMockAppTransition;
381    private MockValueAnimator mMockAnimator;
382    private TestBoundsAnimationTarget mTarget;
383    private BoundsAnimationController mController;
384    private BoundsAnimationDriver mDriver;
385
386    // Temp
387    private Rect mTmpRect = new Rect();
388
389    @Override
390    public void setUp() throws Exception {
391        super.setUp();
392
393        final Context context = InstrumentationRegistry.getTargetContext();
394        final Handler handler = new Handler(Looper.getMainLooper());
395        mMockAppTransition = new MockAppTransition(context);
396        mMockAnimator = new MockValueAnimator();
397        mTarget = new TestBoundsAnimationTarget();
398        mController = new BoundsAnimationController(context, mMockAppTransition, handler, null);
399        mDriver = new BoundsAnimationDriver(mController, mTarget);
400    }
401
402    /** BASE TRANSITIONS **/
403
404    @UiThreadTest
405    @Test
406    public void testFullscreenToFloatingTransition() throws Exception {
407        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
408                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
409                .update(0f)
410                .update(0.5f)
411                .update(1f)
412                .end()
413                .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
414    }
415
416    @UiThreadTest
417    @Test
418    public void testFloatingToFullscreenTransition() throws Exception {
419        mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
420                .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
421                .update(0f)
422                .update(0.5f)
423                .update(1f)
424                .end()
425                .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN);
426    }
427
428    @UiThreadTest
429    @Test
430    public void testFloatingToSmallerFloatingTransition() throws Exception {
431        mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING)
432                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
433                .update(0f)
434                .update(0.5f)
435                .update(1f)
436                .end()
437                .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
438    }
439
440    @UiThreadTest
441    @Test
442    public void testFloatingToLargerFloatingTransition() throws Exception {
443        mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING)
444                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
445                .update(0f)
446                .update(0.5f)
447                .update(1f)
448                .end()
449                .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
450    }
451
452    /** F->!F w/ CANCEL **/
453
454    @UiThreadTest
455    @Test
456    public void testFullscreenToFloatingCancelFromTarget() throws Exception {
457        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
458                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
459                .update(0.25f)
460                .cancel()
461                .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
462    }
463
464    @UiThreadTest
465    @Test
466    public void testFullscreenToFloatingCancelFromAnimationToSameBounds() throws Exception {
467        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
468                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
469                .update(0.25f)
470                .restart(BOUNDS_FLOATING)
471                .end()
472                .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
473    }
474
475    @UiThreadTest
476    @Test
477    public void testFullscreenToFloatingCancelFromAnimationToFloatingBounds() throws Exception {
478        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
479                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
480                .update(0.25f)
481                .restart(BOUNDS_SMALLER_FLOATING)
482                .end()
483                .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
484    }
485
486    @UiThreadTest
487    @Test
488    public void testFullscreenToFloatingCancelFromAnimationToFullscreenBounds() throws Exception {
489        mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
490                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
491                .update(0.25f)
492                .restart(BOUNDS_FULL)
493                .end()
494                .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN);
495    }
496
497    /** !F->F w/ CANCEL **/
498
499    @UiThreadTest
500    @Test
501    public void testFloatingToFullscreenCancelFromTarget() throws Exception {
502        mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
503                .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
504                .update(0.25f)
505                .cancel()
506                .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
507    }
508
509    @UiThreadTest
510    @Test
511    public void testFloatingToFullscreenCancelFromAnimationToSameBounds() throws Exception {
512        mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
513                .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
514                .update(0.25f)
515                .restart(BOUNDS_FULL)
516                .end()
517                .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN);
518    }
519
520    @UiThreadTest
521    @Test
522    public void testFloatingToFullscreenCancelFromAnimationToFloatingBounds() throws Exception {
523        mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
524                .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
525                .update(0.25f)
526                .restart(BOUNDS_SMALLER_FLOATING)
527                .end()
528                .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
529    }
530
531    /** !F->!F w/ CANCEL **/
532
533    @UiThreadTest
534    @Test
535    public void testFloatingToSmallerFloatingCancelFromTarget() throws Exception {
536        mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_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    @UiThreadTest
544    @Test
545    public void testFloatingToLargerFloatingCancelFromTarget() throws Exception {
546        mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING)
547                .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
548                .update(0.25f)
549                .cancel()
550                .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
551    }
552
553    /** MISC **/
554
555    @UiThreadTest
556    @Test
557    public void testBoundsAreCopied() throws Exception {
558        Rect from = new Rect(0, 0, 100, 100);
559        Rect to = new Rect(25, 25, 75, 75);
560        mDriver.start(from, to)
561                .update(0.25f)
562                .end();
563        assertEquals(new Rect(0, 0, 100, 100), from);
564        assertEquals(new Rect(25, 25, 75, 75), to);
565    }
566
567    /**
568     * @return whether the task and stack bounds would be the same if they were at the same offset.
569     */
570    private boolean assertEqualSizeAtOffset(Rect stackBounds, Rect taskBounds) {
571        mTmpRect.set(taskBounds);
572        mTmpRect.offsetTo(stackBounds.left, stackBounds.top);
573        return stackBounds.equals(mTmpRect);
574    }
575}
576