Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 475fe3f1 authored by Jorim Jaggi's avatar Jorim Jaggi Committed by Android (Google) Code Review
Browse files

Merge "Wait with reparenting back until all app animations are done" into pi-dev

parents 1486849e 6de6101c
Loading
Loading
Loading
Loading
+91 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */

package com.android.server.wm;

import android.util.ArrayMap;
import android.util.ArraySet;

import java.util.ArrayList;

/**
 * Keeps track of all {@link AppWindowToken} that are animating and makes sure all animations are
 * finished at the same time such that we don't run into issues with z-ordering: An activity A
 * that has a shorter animation that is above another activity B with a longer animation in the same
 * task, the animation layer would put the B on top of A, but from the hierarchy, A needs to be on
 * top of B. Thus, we defer reparenting A to the original hierarchy such that it stays on top of B
 * until B finishes animating.
 */
class AnimatingAppWindowTokenRegistry {

    private ArraySet<AppWindowToken> mAnimatingTokens = new ArraySet<>();
    private ArrayMap<AppWindowToken, Runnable> mFinishedTokens = new ArrayMap<>();

    private ArrayList<Runnable> mTmpRunnableList = new ArrayList<>();

    /**
     * Notifies that an {@link AppWindowToken} has started animating.
     */
    void notifyStarting(AppWindowToken token) {
        mAnimatingTokens.add(token);
    }

    /**
     * Notifies that an {@link AppWindowToken} has finished animating.
     */
    void notifyFinished(AppWindowToken token) {
        mAnimatingTokens.remove(token);
        mFinishedTokens.remove(token);
    }

    /**
     * Called when an {@link AppWindowToken} is about to finish animating.
     *
     * @param endDeferFinishCallback Callback to run when defer finish should be ended.
     * @return {@code true} if finishing the animation should be deferred, {@code false} otherwise.
     */
    boolean notifyAboutToFinish(AppWindowToken token, Runnable endDeferFinishCallback) {
        final boolean removed = mAnimatingTokens.remove(token);
        if (!removed) {
            return false;
        }

        if (mAnimatingTokens.isEmpty()) {

            // If no animations are animating anymore, finish all others.
            endDeferringFinished();
            return false;
        } else {

            // Otherwise let's put it into the pending list of to be finished animations.
            mFinishedTokens.put(token, endDeferFinishCallback);
            return true;
        }
    }

    private void endDeferringFinished() {
        // Copy it into a separate temp list to avoid modifying the collection while iterating as
        // calling the callback may call back into notifyFinished.
        for (int i = mFinishedTokens.size() - 1; i >= 0; i--) {
            mTmpRunnableList.add(mFinishedTokens.valueAt(i));
        }
        mFinishedTokens.clear();
        for (int i = mTmpRunnableList.size() - 1; i >= 0; i--) {
            mTmpRunnableList.get(i).run();
        }
        mTmpRunnableList.clear();
    }
}
+29 −0
Original line number Diff line number Diff line
@@ -251,6 +251,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
    private final Point mTmpPoint = new Point();
    private final Rect mTmpRect = new Rect();
    private RemoteAnimationDefinition mRemoteAnimationDefinition;
    private AnimatingAppWindowTokenRegistry mAnimatingAppWindowTokenRegistry;

    AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction,
            DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen,
@@ -780,6 +781,16 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
                task.mStack.mExitingAppTokens.remove(this);
            }
        }
        final TaskStack stack = getStack();

        // If we reparent, make sure to remove ourselves from the old animation registry.
        if (mAnimatingAppWindowTokenRegistry != null) {
            mAnimatingAppWindowTokenRegistry.notifyFinished(this);
        }
        mAnimatingAppWindowTokenRegistry = stack != null
                ? stack.getAnimatingAppWindowTokenRegistry()
                : null;

        mLastParent = task;
    }

@@ -1783,6 +1794,21 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
        return a;
    }

    @Override
    public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
        return mAnimatingAppWindowTokenRegistry != null
                && mAnimatingAppWindowTokenRegistry.notifyAboutToFinish(
                        this, endDeferFinishCallback);
    }

    @Override
    public void onAnimationLeashDestroyed(Transaction t) {
        super.onAnimationLeashDestroyed(t);
        if (mAnimatingAppWindowTokenRegistry != null) {
            mAnimatingAppWindowTokenRegistry.notifyFinished(this);
        }
    }

    @Override
    protected void setLayer(Transaction t, int layer) {
        if (!mSurfaceAnimator.hasLeash()) {
@@ -1825,6 +1851,9 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree

        final DisplayContent dc = getDisplayContent();
        dc.assignStackOrdering(t);
        if (mAnimatingAppWindowTokenRegistry != null) {
            mAnimatingAppWindowTokenRegistry.notifyStarting(this);
        }
    }

    /**
+20 −3
Original line number Diff line number Diff line
@@ -81,10 +81,15 @@ class SurfaceAnimator {
                if (anim != mAnimation) {
                    return;
                }
                final Runnable resetAndInvokeFinish = () -> {
                    reset(mAnimatable.getPendingTransaction(), true /* destroyLeash */);
                    if (animationFinishedCallback != null) {
                        animationFinishedCallback.run();
                    }
                };
                if (!mAnimatable.shouldDeferAnimationFinish(resetAndInvokeFinish)) {
                    resetAndInvokeFinish.run();
                }
            }
        };
    }
@@ -407,5 +412,17 @@ class SurfaceAnimator {
         * @return The height of the surface to be animated.
         */
        int getSurfaceHeight();

        /**
         * Gets called when the animation is about to finish and gives the client the opportunity to
         * defer finishing the animation, i.e. it keeps the leash around until the client calls
         * {@link #cancelAnimation}.
         *
         * @param endDeferFinishCallback The callback to call when defer finishing should be ended.
         * @return Whether the client would like to defer the animation finish.
         */
        default boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
            return false;
        }
    }
}
+7 −0
Original line number Diff line number Diff line
@@ -155,6 +155,9 @@ public class TaskStack extends WindowContainer<Task> implements
    final Rect mTmpDimBoundsRect = new Rect();
    private final Point mLastSurfaceSize = new Point();

    private final AnimatingAppWindowTokenRegistry mAnimatingAppWindowTokenRegistry =
            new AnimatingAppWindowTokenRegistry();

    TaskStack(WindowManagerService service, int stackId, StackWindowController controller) {
        super(service);
        mStackId = stackId;
@@ -1782,4 +1785,8 @@ public class TaskStack extends WindowContainer<Task> implements
        outPos.x -= outset;
        outPos.y -= outset;
    }

    AnimatingAppWindowTokenRegistry getAnimatingAppWindowTokenRegistry() {
        return mAnimatingAppWindowTokenRegistry;
    }
}
+91 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */

package com.android.server.wm;

import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;

import android.platform.test.annotations.Presubmit;
import android.support.test.filters.FlakyTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;

import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

/**
 * Tests for the {@link TaskStack} class.
 *
 * Build/Install/Run:
 *  atest FrameworksServicesTests:com.android.server.wm.AnimatingAppWindowTokenRegistryTest
 */
@SmallTest
@Presubmit
@FlakyTest(detail = "Promote once confirmed non-flaky")
@RunWith(AndroidJUnit4.class)
public class AnimatingAppWindowTokenRegistryTest extends WindowTestsBase {

    @Mock
    AnimationAdapter mAdapter;

    @Mock
    Runnable mMockEndDeferFinishCallback1;
    @Mock
    Runnable mMockEndDeferFinishCallback2;
    @Before
    public void setUp() throws Exception {
        super.setUp();
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testDeferring() throws Exception {
        final AppWindowToken window1 = createAppWindowToken(mDisplayContent,
                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
        final AppWindowToken window2 = createAppWindow(window1.getTask(), ACTIVITY_TYPE_STANDARD,
                "window2").mAppToken;
        final AnimatingAppWindowTokenRegistry registry =
                window1.getStack().getAnimatingAppWindowTokenRegistry();

        window1.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */);
        window2.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */);
        assertTrue(window1.isSelfAnimating());
        assertTrue(window2.isSelfAnimating());

        // Make sure that first animation finish is deferred, second one is not deferred, and first
        // one gets cancelled.
        assertTrue(registry.notifyAboutToFinish(window1, mMockEndDeferFinishCallback1));
        assertFalse(registry.notifyAboutToFinish(window2, mMockEndDeferFinishCallback2));
        verify(mMockEndDeferFinishCallback1).run();
        verifyZeroInteractions(mMockEndDeferFinishCallback2);
    }
}
Loading