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

Commit c335c3cf authored by Jorim Jaggi's avatar Jorim Jaggi Committed by android-build-merger
Browse files

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

am: 475fe3f1

Change-Id: If88d3ff52402d14e5ae5eb4d8f4d1e849f6f9507
parents 6b031adf 475fe3f1
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