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

Commit a6ea1833 authored by Winson Chung's avatar Winson Chung
Browse files

Migrating some animation spec logic to shared lib

- Used for the current transition to/from Recents and for docking, move the
  async app transition animation spec logic to the lib.  It will be removed
  later once we have app controled surface controls.

Bug: 67510855
Test: Launch recents/app, dock app from dragging

Change-Id: I1cf972991e5ef73906c3bcd8e5ff187ce2e62cd8
parent 4f8bd78b
Loading
Loading
Loading
Loading
+84 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.systemui.shared.recents.view;

import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.view.AppTransitionAnimationSpec;
import android.view.IAppTransitionAnimationSpecsFuture;

import java.util.List;
import java.util.concurrent.FutureTask;

/**
 * To be implemented by a particular animation to asynchronously provide the animation specs for a
 * particular transition.
 */
public abstract class AppTransitionAnimationSpecsFuture {

    private final Handler mHandler;
    private FutureTask<List<AppTransitionAnimationSpec>> mComposeTask = new FutureTask<>(() -> {
        synchronized (AppTransitionAnimationSpecsFuture.this) {
            return composeSpecs();
        }
    });

    private final IAppTransitionAnimationSpecsFuture mFuture =
            new IAppTransitionAnimationSpecsFuture.Stub() {
        @Override
        public AppTransitionAnimationSpec[] get() throws RemoteException {
            try {
                if (!mComposeTask.isDone()) {
                    mHandler.post(mComposeTask);
                }
                List<AppTransitionAnimationSpec> specs = mComposeTask.get();
                if (specs == null) {
                    return null;
                }

                AppTransitionAnimationSpec[] arr = new AppTransitionAnimationSpec[specs.size()];
                specs.toArray(arr);
                return arr;
            } catch (Exception e) {
                return null;
            }
        }
    };

    public AppTransitionAnimationSpecsFuture(Handler handler) {
        mHandler = handler;
    }

    /**
     * Returns the future to handle the call from window manager.
     */
    public final IAppTransitionAnimationSpecsFuture getFuture() {
        return mFuture;
    }

    /**
     * Called ahead of the future callback to compose the specs to be returned in the future.
     */
    public final void composeSpecsSynchronous() {
        if (Looper.myLooper() != mHandler.getLooper()) {
            throw new RuntimeException("composeSpecsSynchronous() called from wrong looper");
        }
        mComposeTask.run();
    }

    public abstract List<AppTransitionAnimationSpec> composeSpecs();
}
+117 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.systemui.shared.recents.view;

import android.app.ActivityOptions;
import android.app.ActivityOptions.OnAnimationStartedListener;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.GraphicBuffer;
import android.os.Bundle;
import android.os.Handler;
import android.os.IRemoteCallback;
import android.os.RemoteException;
import android.view.DisplayListCanvas;
import android.view.RenderNode;
import android.view.ThreadedRenderer;
import android.view.View;

import java.util.function.Consumer;

/**
 * A helper class to create transitions to/from an App to Recents.
 */
public class RecentsTransition {

    /**
     * Creates a new transition aspect scaled transition activity options.
     */
    public static ActivityOptions createAspectScaleAnimation(Context context, Handler handler,
            boolean scaleUp, AppTransitionAnimationSpecsFuture animationSpecsFuture,
            OnAnimationStartedListener animationStartCallback) {
        final OnAnimationStartedListener animStartedListener = new OnAnimationStartedListener() {
            private boolean mHandled;

            @Override
            public void onAnimationStarted() {
                // OnAnimationStartedListener can be called numerous times, so debounce here to
                // prevent multiple callbacks
                if (mHandled) {
                    return;
                }
                mHandled = true;

                if (animationStartCallback != null) {
                    animationStartCallback.onAnimationStarted();
                }
            }
        };
        final ActivityOptions opts = ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(
                context, handler,
                animationSpecsFuture != null ? animationSpecsFuture.getFuture() : null,
                animStartedListener, scaleUp);
        return opts;
    }

    /**
     * Wraps a animation-start callback in a binder that can be called from window manager.
     */
    public static IRemoteCallback wrapStartedListener(Handler handler,
            OnAnimationStartedListener listener) {
        if (listener == null) {
            return null;
        }
        return new IRemoteCallback.Stub() {
            @Override
            public void sendResult(Bundle data) throws RemoteException {
                handler.post(listener::onAnimationStarted);
            }
        };
    }

    /**
     * @return a {@link GraphicBuffer} with the {@param view} drawn into it. Result can be null if
     *         we were unable to allocate a hardware bitmap.
     */
    public static GraphicBuffer drawViewIntoGraphicBuffer(int width, int height, View view,
            float scale, int eraseColor) {
        final Bitmap hwBitmap = createHardwareBitmap(width, height, (c) -> {
            c.scale(scale, scale);
            if (eraseColor != 0) {
                c.drawColor(eraseColor);
            }
            if (view != null) {
                view.draw(c);
            }
        });
        return hwBitmap != null ? hwBitmap.createGraphicBufferHandle() : null;
    }

    /**
     * @return a hardware {@link Bitmap} after being drawn with the {@param consumer}. Result can be
     *         null if we were unable to allocate a hardware bitmap.
     */
    public static Bitmap createHardwareBitmap(int width, int height, Consumer<Canvas> consumer) {
        RenderNode node = RenderNode.create("RecentsTransition", null);
        node.setLeftTopRightBottom(0, 0, width, height);
        node.setClipToBounds(false);
        DisplayListCanvas c = node.start(width, height);
        consumer.accept(c);
        node.end(c);
        return ThreadedRenderer.createHardwareBitmap(node, width, height);
    }
}
+19 −18
Original line number Diff line number Diff line
@@ -77,19 +77,20 @@ import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.recents.views.RecentsTransitionHelper;
import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
import com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport;
import com.android.systemui.recents.views.TaskStackView;
import com.android.systemui.recents.views.TaskViewHeader;
import com.android.systemui.recents.views.TaskViewTransform;
import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
import com.android.systemui.shared.recents.view.RecentsTransition;
import com.android.systemui.stackdivider.DividerView;
import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
import com.android.systemui.statusbar.phone.StatusBar;

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

/**
 * An implementation of the Recents component for the current user.  For secondary users, this can
@@ -864,22 +865,22 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
                windowOverrideRect);

        RectF toTaskRect = toTransform.rect;
        AppTransitionAnimationSpecsFuture future =
                new RecentsTransitionHelper(mContext).getAppTransitionFuture(
                        () -> {
        AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture(mHandler) {
            @Override
            public List<AppTransitionAnimationSpec> composeSpecs() {
                Rect rect = new Rect();
                toTaskRect.round(rect);
                    GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(toTask,
                            toTransform);
                    return Lists.newArrayList(new AppTransitionAnimationSpec(
                            toTask.key.id, thumbnail, rect));
                });
                GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
                return Lists.newArrayList(new AppTransitionAnimationSpec(toTask.key.id, thumbnail,
                        rect));
            }
        };

        // For low end ram devices, wait for transition flag is reset when Recents entrance
        // animation is complete instead of when the transition animation starts
        return new Pair<>(ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
                mHandler, future.getFuture(), isLowRamDevice ? null : mResetToggleFlagListener,
                false /* scaleUp */), future);
        return new Pair<>(RecentsTransition.createAspectScaleAnimation(mContext, mHandler,
                false /* scaleUp */, future, isLowRamDevice ? null : mResetToggleFlagListener),
                future);
    }

    /**
@@ -919,7 +920,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
                boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
                mHeaderBar.onTaskViewSizeChanged(width, height);
                if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
                    return RecentsTransitionHelper.drawViewIntoGraphicBuffer(width, mTaskBarHeight,
                    return RecentsTransition.drawViewIntoGraphicBuffer(width, mTaskBarHeight,
                            null, 1f, 0xFFff0000);
                } else {
                    // Workaround for b/27815919, reset the callback so that we do not trigger an
@@ -932,7 +933,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
                            disabledInSafeMode);
                    mHeaderBar.onTaskDataLoaded();
                    mHeaderBar.setDimAlpha(toTransform.dimAlpha);
                    return RecentsTransitionHelper.drawViewIntoGraphicBuffer(width, mTaskBarHeight,
                    return RecentsTransition.drawViewIntoGraphicBuffer(width, mTaskBarHeight,
                            mHeaderBar, 1f, 0);
                }
            }
@@ -1047,7 +1048,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
            Recents.getSystemServices().startActivityAsUserAsync(intent, opts);
            EventBus.getDefault().send(new RecentsActivityStartingEvent());
            if (future != null) {
                future.precacheSpecs();
                future.composeSpecsSynchronous();
            }
        });
        EventBus.getDefault().send(hideMenuEvent);
+175 −0

File changed and moved.

Preview size limit exceeded, changes collapsed.

+164 −18
Original line number Diff line number Diff line
@@ -16,8 +16,12 @@

package com.android.systemui.recents.views;

import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY;

import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.app.ActivityOptions.OnAnimationStartedListener;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -28,6 +32,7 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.MathUtils;
@@ -54,15 +59,22 @@ import com.android.systemui.recents.RecentsActivity;
import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
import com.android.systemui.recents.events.activity.LaunchTaskEvent;
import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent;
import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
import com.android.systemui.recents.events.activity.ShowEmptyViewEvent;
import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
import com.android.systemui.recents.events.component.ExpandPipEvent;
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
@@ -76,8 +88,8 @@ import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecComposer;
import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
import com.android.systemui.shared.recents.view.RecentsTransition;
import com.android.systemui.stackdivider.WindowManagerProxy;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.phone.ScrimController;
@@ -101,6 +113,7 @@ public class RecentsView extends FrameLayout {

    private static final int BUSY_RECENTS_TASK_COUNT = 3;

    private Handler mHandler;
    private TaskStackView mTaskStackView;
    private TextView mStackActionButton;
    private TextView mEmptyView;
@@ -126,7 +139,7 @@ public class RecentsView extends FrameLayout {
        mMultiWindowBackgroundScrim.setAlpha(alpha);
    };

    private RecentsTransitionHelper mTransitionHelper;
    private RecentsTransitionComposer mTransitionHelper;
    @ViewDebug.ExportedProperty(deepExport=true, prefix="touch_")
    private RecentsViewTouchHandler mTouchHandler;
    private final FlingAnimationUtils mFlingAnimationUtils;
@@ -148,7 +161,8 @@ public class RecentsView extends FrameLayout {
        setWillNotDraw(false);

        SystemServicesProxy ssp = Recents.getSystemServices();
        mTransitionHelper = new RecentsTransitionHelper(getContext());
        mHandler = new Handler();
        mTransitionHelper = new RecentsTransitionComposer(getContext());
        mDividerSize = ssp.getDockedDividerSize(context);
        mTouchHandler = new RecentsViewTouchHandler(this);
        mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
@@ -518,9 +532,8 @@ public class RecentsView extends FrameLayout {
    /**** EventBus Events ****/

    public final void onBusEvent(LaunchTaskEvent event) {
        mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView,
                event.taskView, event.screenPinningRequested, event.targetWindowingMode,
                event.targetActivityType);
        launchTaskFromRecents(getStack(), event.task, mTaskStackView, event.taskView,
                event.screenPinningRequested, event.targetWindowingMode, event.targetActivityType);
        if (Recents.getConfiguration().isLowRamDevice) {
            EventBus.getDefault().send(new HideStackActionButtonEvent(false /* translate */));
        }
@@ -607,17 +620,15 @@ public class RecentsView extends FrameLayout {
                };

                final Rect taskRect = getTaskRect(event.taskView);
                AppTransitionAnimationSpecsFuture future =
                        mTransitionHelper.getAppTransitionFuture(
                                new AnimationSpecComposer() {
                AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture(
                        getHandler()) {
                    @Override
                    public List<AppTransitionAnimationSpec> composeSpecs() {
                                        return mTransitionHelper.composeDockAnimationSpec(
                                                event.taskView, taskRect);
                        return mTransitionHelper.composeDockAnimationSpec(event.taskView, taskRect);
                    }
                                });
                };
                ssp.overridePendingAppTransitionMultiThumbFuture(future.getFuture(),
                        mTransitionHelper.wrapStartedListener(startedListener),
                        RecentsTransition.wrapStartedListener(getHandler(), startedListener),
                        true /* scaleUp */);

                MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP,
@@ -881,7 +892,8 @@ public class RecentsView extends FrameLayout {
     * @return the bounds of the stack action button.
     */
    Rect getStackActionButtonBoundsFromStackLayout() {
        Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
        Rect actionButtonRect = new Rect(
                mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
        int left, top;
        if (Recents.getConfiguration().isLowRamDevice) {
            Rect windowRect = Recents.getSystemServices().getWindowRect();
@@ -906,6 +918,140 @@ public class RecentsView extends FrameLayout {
        return mStackActionButton;
    }

    /**
     * Launches the specified {@link Task}.
     */
    public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
            final TaskStackView stackView, final TaskView taskView,
            final boolean screenPinningRequested, final int windowingMode, final int activityType) {

        final ActivityOptions.OnAnimationStartedListener animStartedListener;
        final AppTransitionAnimationSpecsFuture transitionFuture;
        if (taskView != null) {

            // Fetch window rect here already in order not to be blocked on lock contention in WM
            // when the future calls it.
            final Rect windowRect = Recents.getSystemServices().getWindowRect();
            transitionFuture = new AppTransitionAnimationSpecsFuture(stackView.getHandler()) {
                @Override
                public List<AppTransitionAnimationSpec> composeSpecs() {
                    return mTransitionHelper.composeAnimationSpecs(task, stackView, windowingMode,
                            activityType, windowRect);
                }
            };
            animStartedListener = new OnAnimationStartedListener() {
                private boolean mHandled;

                @Override
                public void onAnimationStarted() {
                    if (mHandled) {
                        return;
                    }
                    mHandled = true;

                    // If we are launching into another task, cancel the previous task's
                    // window transition
                    EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
                    stackView.cancelAllTaskViewAnimations();

                    if (screenPinningRequested) {
                        // Request screen pinning after the animation runs
                        mHandler.postDelayed(() -> {
                            EventBus.getDefault().send(new ScreenPinningRequestEvent(mContext,
                                    task.key.id));
                        }, 350);
                    }

                    if (!Recents.getConfiguration().isLowRamDevice) {
                        // Reset the state where we are waiting for the transition to start
                        EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
                    }
                }
            };
        } else {
            // This is only the case if the task is not on screen (scrolled offscreen for example)
            transitionFuture = null;
            animStartedListener = new OnAnimationStartedListener() {
                private boolean mHandled;

                @Override
                public void onAnimationStarted() {
                    if (mHandled) {
                        return;
                    }
                    mHandled = true;

                    // If we are launching into another task, cancel the previous task's
                    // window transition
                    EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
                    stackView.cancelAllTaskViewAnimations();

                    if (!Recents.getConfiguration().isLowRamDevice) {
                        // Reset the state where we are waiting for the transition to start
                        EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
                    }
                }
            };
        }

        EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(true));
        final ActivityOptions opts = RecentsTransition.createAspectScaleAnimation(mContext,
                mHandler, true /* scaleUp */, transitionFuture != null ? transitionFuture : null,
                animStartedListener);
        if (taskView == null) {
            // If there is no task view, then we do not need to worry about animating out occluding
            // task views, and we can launch immediately
            startTaskActivity(stack, task, taskView, opts, transitionFuture,
                    windowingMode, activityType);
        } else {
            LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView,
                    screenPinningRequested);
            EventBus.getDefault().send(launchStartedEvent);
            startTaskActivity(stack, task, taskView, opts, transitionFuture, windowingMode,
                    activityType);
        }
        Recents.getSystemServices().sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
    }

    /**
     * Starts the activity for the launch task.
     *
     * @param taskView this is the {@link TaskView} that we are launching from. This can be null if
     *                 we are toggling recents and the launch-to task is now offscreen.
     */
    private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView,
            ActivityOptions opts, AppTransitionAnimationSpecsFuture transitionFuture,
            int windowingMode, int activityType) {
        SystemServicesProxy ssp = Recents.getSystemServices();
        ssp.startActivityFromRecents(mContext, task.key, task.title, opts, windowingMode,
                activityType,
                succeeded -> {
                    if (succeeded) {
                        // Keep track of the index of the task launch
                        int taskIndexFromFront = 0;
                        int taskIndex = stack.indexOfStackTask(task);
                        if (taskIndex > -1) {
                            taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
                        }
                        EventBus.getDefault().send(new LaunchTaskSucceededEvent(
                                taskIndexFromFront));
                    } else {
                        // Dismiss the task if we fail to launch it
                        if (taskView != null) {
                            taskView.dismissTask();
                        }

                        // Keep track of failed launches
                        EventBus.getDefault().send(new LaunchTaskFailedEvent());
                    }
                });
        if (transitionFuture != null) {
            mHandler.post(transitionFuture::composeSpecsSynchronous);
        }
    }

    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        super.requestDisallowInterceptTouchEvent(disallowIntercept);
Loading