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

Commit f2a23c72 authored by Evan Rosky's avatar Evan Rosky Committed by Android (Google) Code Review
Browse files

Merge "Refactor default transit animations into handler for testing"

parents 1f6edbda 1229c01a
Loading
Loading
Loading
Loading
+149 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.wm.shell.transition;

import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.os.IBinder;
import android.util.ArrayMap;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.WindowContainerTransaction;

import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;

import java.util.ArrayList;

/** The default handler that handles anything not already handled. */
public class DefaultTransitionHandler implements Transitions.TransitionHandler {
    private final TransactionPool mTransactionPool;
    private final ShellExecutor mMainExecutor;
    private final ShellExecutor mAnimExecutor;

    /** Keeps track of the currently-running animations associated with each transition. */
    private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>();

    DefaultTransitionHandler(@NonNull TransactionPool transactionPool,
            @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
        mTransactionPool = transactionPool;
        mMainExecutor = mainExecutor;
        mAnimExecutor = animExecutor;
    }

    @Override
    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {
        if (mAnimations.containsKey(transition)) {
            throw new IllegalStateException("Got a duplicate startAnimation call for "
                    + transition);
        }
        final ArrayList<Animator> animations = new ArrayList<>();
        mAnimations.put(transition, animations);
        final boolean isOpening = Transitions.isOpeningType(info.getType());

        final Runnable onAnimFinish = () -> {
            if (!animations.isEmpty()) return;
            mAnimations.remove(transition);
            finishCallback.run();
        };
        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
            final TransitionInfo.Change change = info.getChanges().get(i);

            // Don't animate anything with an animating parent
            if (change.getParent() != null) continue;

            final int mode = change.getMode();
            if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
                if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
                    // This received a transferred starting window, so don't animate
                    continue;
                }
                // fade in
                startExampleAnimation(
                        animations, change.getLeash(), true /* show */, onAnimFinish);
            } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
                // fade out
                startExampleAnimation(
                        animations, change.getLeash(), false /* show */, onAnimFinish);
            }
        }
        t.apply();
        // run finish now in-case there are no animations
        onAnimFinish.run();
        return true;
    }

    @Nullable
    @Override
    public WindowContainerTransaction handleRequest(int type, @NonNull IBinder transition,
            @Nullable ActivityManager.RunningTaskInfo triggerTask) {
        return null;
    }

    // TODO(shell-transitions): real animations
    private void startExampleAnimation(@NonNull ArrayList<Animator> animations,
            @NonNull SurfaceControl leash, boolean show, @NonNull Runnable finishCallback) {
        final float end = show ? 1.f : 0.f;
        final float start = 1.f - end;
        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
        final ValueAnimator va = ValueAnimator.ofFloat(start, end);
        va.setDuration(500);
        va.addUpdateListener(animation -> {
            float fraction = animation.getAnimatedFraction();
            transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
            transaction.apply();
        });
        final Runnable finisher = () -> {
            transaction.setAlpha(leash, end);
            transaction.apply();
            mTransactionPool.release(transaction);
            mMainExecutor.execute(() -> {
                animations.remove(va);
                finishCallback.run();
            });
        };
        va.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) { }

            @Override
            public void onAnimationEnd(Animator animation) {
                finisher.run();
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                finisher.run();
            }

            @Override
            public void onAnimationRepeat(Animator animation) { }
        });
        animations.add(va);
        mAnimExecutor.execute(va::start);
    }
}
+18 −81
Original line number Diff line number Diff line
@@ -23,8 +23,6 @@ import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -41,6 +39,7 @@ import android.window.WindowOrganizer;

import androidx.annotation.BinderThread;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
@@ -48,6 +47,7 @@ import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup;

import java.util.ArrayList;
import java.util.Arrays;

/** Plays transition animations */
public class Transitions {
@@ -58,7 +58,6 @@ public class Transitions {
            SystemProperties.getBoolean("persist.debug.shell_transit", false);

    private final WindowOrganizer mOrganizer;
    private final TransactionPool mTransactionPool;
    private final ShellExecutor mMainExecutor;
    private final ShellExecutor mAnimExecutor;
    private final TransitionPlayerImpl mPlayerImpl;
@@ -67,7 +66,6 @@ public class Transitions {
    private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();

    private static final class ActiveTransition {
        ArrayList<Animator> mAnimations = null;
        TransitionHandler mFirstHandler = null;
    }

@@ -77,10 +75,11 @@ public class Transitions {
    public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
            @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
        mOrganizer = organizer;
        mTransactionPool = pool;
        mMainExecutor = mainExecutor;
        mAnimExecutor = animExecutor;
        mPlayerImpl = new TransitionPlayerImpl();
        // The very last handler (0 in the list) should be the default one.
        mHandlers.add(new DefaultTransitionHandler(pool, mainExecutor, animExecutor));
    }

    /** Register this transition handler with Core */
@@ -104,47 +103,10 @@ public class Transitions {
        return mAnimExecutor;
    }

    // TODO(shell-transitions): real animations
    private void startExampleAnimation(@NonNull IBinder transition, @NonNull SurfaceControl leash,
            boolean show) {
        final float end = show ? 1.f : 0.f;
        final float start = 1.f - end;
        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
        final ValueAnimator va = ValueAnimator.ofFloat(start, end);
        va.setDuration(500);
        va.addUpdateListener(animation -> {
            float fraction = animation.getAnimatedFraction();
            transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
            transaction.apply();
        });
        final Runnable finisher = () -> {
            transaction.setAlpha(leash, end);
            transaction.apply();
            mTransactionPool.release(transaction);
            mMainExecutor.execute(() -> {
                mActiveTransitions.get(transition).mAnimations.remove(va);
                onFinish(transition);
            });
        };
        va.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) { }

            @Override
            public void onAnimationEnd(Animator animation) {
                finisher.run();
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                finisher.run();
            }

            @Override
            public void onAnimationRepeat(Animator animation) { }
        });
        mActiveTransitions.get(transition).mAnimations.add(va);
        mAnimExecutor.execute(va::start);
    /** Only use this in tests. This is used to avoid running animations during tests. */
    @VisibleForTesting
    void replaceDefaultHandlerForTest(TransitionHandler handler) {
        mHandlers.set(0, handler);
    }

    /** @return true if the transition was triggered by opening something vs closing something */
@@ -217,18 +179,16 @@ public class Transitions {
        }
    }

    private void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
    @VisibleForTesting
    void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction t) {
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
                transitionToken, info);
        final ActiveTransition active = mActiveTransitions.get(transitionToken);
        if (active == null) {
            throw new IllegalStateException("Got transitionReady for non-active transition "
                    + transitionToken + ". expecting one of " + mActiveTransitions.keySet());
        }
        if (active.mAnimations != null) {
            throw new IllegalStateException("Got a duplicate onTransitionReady call for "
                    + transitionToken);
                    + transitionToken + ". expecting one of "
                    + Arrays.toString(mActiveTransitions.keySet().toArray()));
        }
        if (!info.getRootLeash().isValid()) {
            // Invalid root-leash implies that the transition is empty/no-op, so just do
@@ -253,44 +213,21 @@ public class Transitions {
                return;
            }
        }

        // No handler chose to perform this animation, so fall-back to the
        // default animation handling.
        final boolean isOpening = isOpeningType(info.getType());
        active.mAnimations = new ArrayList<>(); // Play fade animations
        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
            final TransitionInfo.Change change = info.getChanges().get(i);

            // Don't animate anything with an animating parent
            if (change.getParent() != null) continue;

            final int mode = info.getChanges().get(i).getMode();
            if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
                if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
                    // This received a transferred starting window, so don't animate
                    continue;
                }
                // fade in
                startExampleAnimation(transitionToken, change.getLeash(), true /* show */);
            } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
                // fade out
                startExampleAnimation(transitionToken, change.getLeash(), false /* show */);
            }
        }
        t.apply();
        onFinish(transitionToken);
        throw new IllegalStateException(
                "This shouldn't happen, maybe the default handler is broken.");
    }

    private void onFinish(IBinder transition) {
        final ActiveTransition active = mActiveTransitions.get(transition);
        if (active.mAnimations != null && !active.mAnimations.isEmpty()) return;
        if (!mActiveTransitions.containsKey(transition)) {
            throw new IllegalStateException("Trying to finish an already-finished transition.");
        }
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                "Transition animations finished, notifying core %s", transition);
        mActiveTransitions.remove(transition);
        mOrganizer.finishTransition(transition, null, null);
    }

    private void requestStartTransition(int type, @NonNull IBinder transitionToken,
    void requestStartTransition(int type, @NonNull IBinder transitionToken,
            @Nullable ActivityManager.RunningTaskInfo triggerTask) {
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: type=%d %s",
                type, transitionToken);
+51 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.wm.shell;

import com.android.wm.shell.common.ShellExecutor;

import java.util.ArrayList;

/**
 * Really basic test executor. It just gathers all events in a blob. The only option is to
 * execute everything at once. If better control over delayed execution is needed, please add it.
 */
public class TestShellExecutor implements ShellExecutor {
    final ArrayList<Runnable> mRunnables = new ArrayList<>();

    @Override
    public void execute(Runnable runnable) {
        mRunnables.add(runnable);
    }

    @Override
    public void executeDelayed(Runnable r, long delayMillis) {
        mRunnables.add(r);
    }

    @Override
    public void removeCallbacks(Runnable r) {
        mRunnables.remove(r);
    }

    public void flushAll() {
        for (int i = mRunnables.size() - 1; i >= 0; --i) {
            mRunnables.get(i).run();
        }
        mRunnables.clear();
    }
}
+243 −0

File added.

Preview size limit exceeded, changes collapsed.