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

Commit bd6816c6 authored by Chris Li's avatar Chris Li Committed by Android (Google) Code Review
Browse files

Merge "Handle remote animation for split API" into sc-v2-dev

parents c31f380a 211d6cf7
Loading
Loading
Loading
Loading
+69 −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 androidx.window.extensions.organizer;

import android.view.Choreographer;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.animation.Animation;
import android.view.animation.Transformation;

import androidx.annotation.NonNull;

/**
 * Wrapper to handle the TaskFragment animation update in one {@link SurfaceControl.Transaction}.
 */
class TaskFragmentAnimationAdapter {
    private final Animation mAnimation;
    private final RemoteAnimationTarget mTarget;
    private final SurfaceControl mLeash;
    private final Transformation mTransformation = new Transformation();
    private final float[] mMatrix = new float[9];
    private boolean mIsFirstFrame = true;

    TaskFragmentAnimationAdapter(@NonNull Animation animation,
            @NonNull RemoteAnimationTarget target) {
        mAnimation = animation;
        mTarget = target;
        mLeash = target.leash;
    }

    /** Called on frame update. */
    void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) {
        if (mIsFirstFrame) {
            t.show(mLeash);
            mIsFirstFrame = false;
        }

        currentPlayTime = Math.min(currentPlayTime, mAnimation.getDuration());
        mAnimation.getTransformation(currentPlayTime, mTransformation);
        mTransformation.getMatrix().postTranslate(
                mTarget.localBounds.left, mTarget.localBounds.top);
        t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
        t.setAlpha(mLeash, mTransformation.getAlpha());
        t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
    }

    /** Called after animation finished. */
    void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
        onAnimationUpdate(t, mAnimation.getDuration());
    }

    long getDurationHint() {
        return mAnimation.computeDurationHint();
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -45,7 +45,7 @@ class TaskFragmentAnimationController {
        }
        final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
        final RemoteAnimationAdapter animationAdapter =
                new RemoteAnimationAdapter(mRemoteRunner, 0, 0);
                new RemoteAnimationAdapter(mRemoteRunner, 0, 0, true /* changeNeedsSnapshot */);
        definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, animationAdapter);
        definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, animationAdapter);
        definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, animationAdapter);
+162 −28
Original line number Diff line number Diff line
@@ -16,8 +16,15 @@

package androidx.window.extensions.organizer;

import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.app.ActivityThread;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
@@ -27,24 +34,41 @@ import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.animation.Animation;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.internal.R;
import com.android.internal.policy.AttributeCache;
import com.android.internal.policy.TransitionAnimation;

import java.util.ArrayList;
import java.util.List;

/** To run the TaskFragment animations. */
class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {

    private static final String TAG = "TaskFragAnimationRunner";
    private final Handler mHandler = new Handler(Looper.myLooper());
    private final TransitionAnimation mTransitionAnimation;

    TaskFragmentAnimationRunner() {
        final Context context = ActivityThread.currentActivityThread().getApplication();
        mTransitionAnimation = new TransitionAnimation(context, false /* debug */, TAG);
        // Initialize the AttributeCache for the TransitionAnimation.
        AttributeCache.init(context);
    }

    @Nullable
    private IRemoteAnimationFinishedCallback mFinishedCallback;
    private Animator mAnimator;

    @Override
    public void onAnimationStart(@WindowManager.TransitionOldType int transit,
            RemoteAnimationTarget[] apps,
            RemoteAnimationTarget[] wallpapers,
            RemoteAnimationTarget[] nonApps,
            IRemoteAnimationFinishedCallback finishedCallback) {
            @NonNull RemoteAnimationTarget[] apps,
            @NonNull RemoteAnimationTarget[] wallpapers,
            @NonNull RemoteAnimationTarget[] nonApps,
            @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
        if (wallpapers.length != 0 || nonApps.length != 0) {
            throw new IllegalArgumentException("TaskFragment shouldn't handle animation with"
                    + "wallpaper or non-app windows.");
@@ -52,7 +76,7 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
        if (TaskFragmentAnimationController.DEBUG) {
            Log.v(TAG, "onAnimationStart transit=" + transit);
        }
        mHandler.post(() -> startAnimation(apps, finishedCallback));
        mHandler.post(() -> startAnimation(transit, apps, finishedCallback));
    }

    @Override
@@ -60,34 +84,144 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
        if (TaskFragmentAnimationController.DEBUG) {
            Log.v(TAG, "onAnimationCancelled");
        }
        mHandler.post(this::onAnimationFinished);
        mHandler.post(this::cancelAnimation);
    }

    private void startAnimation(RemoteAnimationTarget[] targets,
            IRemoteAnimationFinishedCallback finishedCallback) {
        // TODO(b/196173550) replace with actual animations
        mFinishedCallback = finishedCallback;
        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
        for (RemoteAnimationTarget target : targets) {
            if (target.mode == MODE_OPENING) {
                t.show(target.leash);
                t.setAlpha(target.leash, 1);
    /** Creates and starts animation. */
    private void startAnimation(@WindowManager.TransitionOldType int transit,
            @NonNull RemoteAnimationTarget[] targets,
            @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
        if (mAnimator != null) {
            Log.w(TAG, "start new animation when the previous one is not finished yet.");
            mAnimator.cancel();
        }
            t.setPosition(target.leash, target.localBounds.left, target.localBounds.top);
        }
        t.apply();
        onAnimationFinished();
        mAnimator = createAnimator(transit, targets, finishedCallback);
        mAnimator.start();
    }

    private void onAnimationFinished() {
        if (mFinishedCallback == null) {
    /** Cancels animation. */
    private void cancelAnimation() {
        if (mAnimator == null) {
            return;
        }
        mAnimator.cancel();
        mAnimator = null;
    }

    /** Creates the animator given the transition type and windows. */
    private Animator createAnimator(@WindowManager.TransitionOldType int transit,
            @NonNull RemoteAnimationTarget[] targets,
            @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
        final List<TaskFragmentAnimationAdapter> adapters =
                createAnimationAdapters(transit, targets);
        long duration = 0;
        for (TaskFragmentAnimationAdapter adapter : adapters) {
            duration = Math.max(duration, adapter.getDurationHint());
        }
        final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
        animator.setDuration(duration);
        animator.addUpdateListener((anim) -> {
            // Update all adapters in the same transaction.
            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
            for (TaskFragmentAnimationAdapter adapter : adapters) {
                adapter.onAnimationUpdate(t, animator.getCurrentPlayTime());
            }
            t.apply();
        });
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {}

            @Override
            public void onAnimationEnd(Animator animation) {
                final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
                for (TaskFragmentAnimationAdapter adapter : adapters) {
                    adapter.onAnimationEnd(t);
                }
                t.apply();

                try {
            mFinishedCallback.onAnimationFinished();
                    finishedCallback.onAnimationFinished();
                } catch (RemoteException e) {
                    e.rethrowFromSystemServer();
                }
        mFinishedCallback = null;
                mAnimator = null;
            }

            @Override
            public void onAnimationCancel(Animator animation) {}

            @Override
            public void onAnimationRepeat(Animator animation) {}
        });
        return animator;
    }

    /** List of {@link TaskFragmentAnimationAdapter} to handle animations on all window targets. */
    private List<TaskFragmentAnimationAdapter> createAnimationAdapters(
            @WindowManager.TransitionOldType int transit,
            @NonNull RemoteAnimationTarget[] targets) {
        switch (transit) {
            case TRANSIT_OLD_TASK_FRAGMENT_OPEN:
                return createOpenAnimationAdapters(targets);
            case TRANSIT_OLD_TASK_FRAGMENT_CLOSE:
                return createCloseAnimationAdapters(targets);
            case TRANSIT_OLD_TASK_FRAGMENT_CHANGE:
                return createChangeAnimationAdapters(targets);
            default:
                throw new IllegalArgumentException("Unhandled transit type=" + transit);
        }
    }

    private List<TaskFragmentAnimationAdapter> createOpenAnimationAdapters(
            @NonNull RemoteAnimationTarget[] targets) {
        // TODO(b/196173550) We need to customize the animation to handle two open window as one.
        final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
        for (RemoteAnimationTarget target : targets) {
            final Animation animation =
                    loadOpenAnimation(target.mode != MODE_CLOSING /* isEnter */);
            adapters.add(new TaskFragmentAnimationAdapter(animation, target));
        }
        return adapters;
    }

    private List<TaskFragmentAnimationAdapter> createCloseAnimationAdapters(
            @NonNull RemoteAnimationTarget[] targets) {
        // TODO(b/196173550) We need to customize the animation to handle two open window as one.
        final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
        for (RemoteAnimationTarget target : targets) {
            final Animation animation =
                    loadCloseAnimation(target.mode != MODE_CLOSING /* isEnter */);
            adapters.add(new TaskFragmentAnimationAdapter(animation, target));
        }
        return adapters;
    }

    private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters(
            @NonNull RemoteAnimationTarget[] targets) {
        // TODO(b/196173550) We need to hard code the change animation instead of using the default
        //  open. See WindowChangeAnimationSpec.java as an example.
        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
        for (RemoteAnimationTarget target : targets) {
            // The start leash is snapshot of the previous window. Hide it for now, will need to use
            // it for the fade in.
            if (target.startLeash != null) {
                t.hide(target.startLeash);
            }
        }
        t.apply();
        return createOpenAnimationAdapters(targets);
    }

    private Animation loadOpenAnimation(boolean isEnter) {
        return mTransitionAnimation.loadDefaultAnimationAttr(isEnter
                ? R.styleable.WindowAnimation_activityOpenEnterAnimation
                : R.styleable.WindowAnimation_activityOpenExitAnimation);
    }

    private Animation loadCloseAnimation(boolean isEnter) {
        return mTransitionAnimation.loadDefaultAnimationAttr(isEnter
                ? R.styleable.WindowAnimation_activityCloseEnterAnimation
                : R.styleable.WindowAnimation_activityCloseExitAnimation);
    }
}