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

Commit 170192ab authored by Filip Gruszczynski's avatar Filip Gruszczynski
Browse files

Improve animating from recents to multi window state.

Previously all windows would start animating from a single thumbnail
that was clicked. Now each animates from its thumbnail, including
properly animating the thumbnail header. This involves System UI
providing information about the thumbnail setup and app transition code
using it to setup each individual animation.

Change-Id: I6f45c94af6bcbf6898b8ed757d83505af99ed6b1
parent 0dcb9e81
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
/*
** Copyright 2015, 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 android.view;

/** @hide */
parcelable AppTransitionAnimationSpec;
+61 −0
Original line number Diff line number Diff line
package android.view;

import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;

/**
 * Holds information about how the next app transition animation should be executed.
 *
 * This class is intended to be used with IWindowManager.overridePendingAppTransition* methods when
 * simple arguments are not enough to describe the animation.
 *
 * @hide
 */
public class AppTransitionAnimationSpec implements Parcelable {
    public final int taskId;
    public final Bitmap bitmap;
    public final Rect rect;

    public AppTransitionAnimationSpec(int taskId, Bitmap bitmap, Rect rect) {
        this.taskId = taskId;
        this.bitmap = bitmap;
        this.rect = rect;
    }

    public AppTransitionAnimationSpec(Parcel in) {
        taskId = in.readInt();
        bitmap = in.readParcelable(null);
        rect = in.readParcelable(null);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(taskId);
        dest.writeParcelable(bitmap, 0 /* flags */);
        dest.writeParcelable(rect, 0 /* flags */);

    }

    public static final Parcelable.Creator<AppTransitionAnimationSpec> CREATOR
            = new Parcelable.Creator<AppTransitionAnimationSpec>() {
        public AppTransitionAnimationSpec createFromParcel(Parcel in) {
            return new AppTransitionAnimationSpec(in);
        }

        public AppTransitionAnimationSpec[] newArray(int size) {
            return new AppTransitionAnimationSpec[size];
        }
    };

    @Override
    public String toString() {
        return "{taskId: " + taskId + ", bitmap: " + bitmap + ", rect: " + rect + "}";
    }
}
+11 −0
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import android.view.MotionEvent;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.IInputFilter;
import android.view.AppTransitionAnimationSpec;
import android.view.WindowContentFrameStats;

/**
@@ -127,6 +128,16 @@ interface IWindowManager
    void overridePendingAppTransitionAspectScaledThumb(in Bitmap srcThumb, int startX,
            int startY, int targetWidth, int targetHeight, IRemoteCallback startedCallback,
            boolean scaleUp);
    /**
     * Overrides animation for app transition that exits from an application to a multi-window
     * environment and allows specifying transition animation parameters for each window.
     *
     * @param specs Array of transition animation descriptions for entering windows.
     *
     * @hide
     */
    void overridePendingAppTransitionMultiThumb(in AppTransitionAnimationSpec[] specs,
            IRemoteCallback startedCallback, boolean scaleUp);
    void overridePendingAppTransitionInPlace(String packageName, int anim);
    void executeAppTransition();
    void setAppStartingWindow(IBinder token, String pkg, int theme,
+150 −53
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.recents.views;

import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.TaskStackBuilder;
import android.content.Context;
@@ -31,6 +32,8 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.AppTransitionAnimationSpec;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowInsets;
@@ -60,6 +63,8 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV

    private static final String TAG = "RecentsView";

    private static final boolean ADD_HEADER_BITMAP = true;

    /** The RecentsView callbacks */
    public interface RecentsViewCallbacks {
        public void onTaskViewClicked();
@@ -443,23 +448,135 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
        }
    }

    private void postDrawHeaderThumbnailTransitionRunnable(final TaskView tv, final int offsetX,
            final int offsetY, final TaskViewTransform transform,
    private void postDrawHeaderThumbnailTransitionRunnable(final TaskStackView view,
            final TaskView clickedView, final int offsetX, final int offsetY,
            final float stackScroll,
            final ActivityOptions.OnAnimationStartedListener animStartedListener) {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                overrideDrawHeaderThumbnailTransition(view, clickedView, offsetX, offsetY,
                        stackScroll, animStartedListener);

            }
        };

        mCb.runAfterPause(r);
    }

    private void overrideDrawHeaderThumbnailTransition(TaskStackView stackView,
            TaskView clickedTask, int offsetX, int offsetY, float stackScroll,
            final ActivityOptions.OnAnimationStartedListener animStartedListener) {
        List<AppTransitionAnimationSpec> specs = getAppTransitionAnimationSpecs(stackView,
                clickedTask, offsetX, offsetY, stackScroll);
        if (specs == null) {
            return;
        }

        IRemoteCallback.Stub callback = new IRemoteCallback.Stub() {
            @Override
            public void sendResult(Bundle data) throws RemoteException {
                post(new Runnable() {
                    @Override
                    public void run() {
                        if (animStartedListener != null) {
                            animStartedListener.onAnimationStarted();
                        }
                    }
                });
            }
        };

        AppTransitionAnimationSpec[] specsArray =
                new AppTransitionAnimationSpec[specs.size()];
        try {
            WindowManagerGlobal.getWindowManagerService().overridePendingAppTransitionMultiThumb(
                    specs.toArray(specsArray), callback, true /* scaleUp */);

        } catch (RemoteException e) {
            Log.w(TAG, "Error overriding app transition", e);
        }
    }

    private List<AppTransitionAnimationSpec> getAppTransitionAnimationSpecs(TaskStackView stackView,
            TaskView clickedTask, int offsetX, int offsetY, float stackScroll) {
        final int targetStackId = clickedTask.getTask().key.stackId;
        if (targetStackId != ActivityManager.FREEFORM_WORKSPACE_STACK_ID
                && targetStackId != ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID) {
            return null;
        }
        // If this is a full screen stack, the transition will be towards the single, full screen
        // task. We only need the transition spec for this task.
        List<AppTransitionAnimationSpec> specs = new ArrayList<>();
        if (targetStackId == ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID) {
            specs.add(createThumbnailHeaderAnimationSpec(
                    stackView, offsetX, offsetY, stackScroll, clickedTask,
                    clickedTask.getTask().key.id, ADD_HEADER_BITMAP));
            return specs;
        }
        // This is a free form stack or full screen stack, so there will be multiple windows
        // animating from thumbnails. We need transition animation specs for all of them.

        // We will use top and bottom task views as a base for tasks, that aren't visible on the
        // screen. This is necessary for cascade recents list, where some of the tasks might be
        // hidden.
        List<TaskView> taskViews = stackView.getTaskViews();
        int childCount = taskViews.size();
        TaskView topChild = taskViews.get(0);
        TaskView bottomChild = taskViews.get(childCount - 1);
        SparseArray<TaskView> taskViewsByTaskId = new SparseArray<>();
        for (int i = 0; i < childCount; i++) {
            TaskView taskView = taskViews.get(i);
            taskViewsByTaskId.put(taskView.getTask().key.id, taskView);
        }

        TaskStack stack = stackView.getStack();
        // We go through all tasks now and for each generate transition animation spec. If there is
        // a view associated with a task, we use that view as a base for the animation. If there
        // isn't, we use bottom or top view, depending on which one would be closer to the task
        // view if it existed.
        ArrayList<Task> tasks = stack.getTasks();
        boolean passedClickedTask = false;
        for (int i = 0, n = tasks.size(); i < n; i++) {
            Task task = tasks.get(i);
            TaskView taskView = taskViewsByTaskId.get(task.key.id);
            if (taskView != null) {
                specs.add(createThumbnailHeaderAnimationSpec(stackView, offsetX, offsetY,
                        stackScroll, taskView, taskView.getTask().key.id, ADD_HEADER_BITMAP));
                if (taskView == clickedTask) {
                    passedClickedTask = true;
                }
            } else {
                taskView = passedClickedTask ? bottomChild : topChild;
                specs.add(createThumbnailHeaderAnimationSpec(stackView, offsetX, offsetY,
                        stackScroll, taskView, task.key.id, !ADD_HEADER_BITMAP));
            }
        }

        return specs;
    }

    private AppTransitionAnimationSpec createThumbnailHeaderAnimationSpec(TaskStackView stackView,
            int offsetX, int offsetY, float stackScroll, TaskView tv, int taskId,
            boolean addHeaderBitmap) {
        // Disable any focused state before we draw the header
        // Upfront the processing of the thumbnail
        if (tv.isFocusedTask()) {
            tv.unsetFocusedTask();
        }
        TaskViewTransform transform = new TaskViewTransform();
        transform = stackView.getStackAlgorithm().getStackTransform(tv.mTask, stackScroll,
                transform, null);

        float scale = tv.getScaleX();
        int fromHeaderWidth = (int) (tv.mHeaderView.getMeasuredWidth() * scale);
        int fromHeaderHeight = (int) (tv.mHeaderView.getMeasuredHeight() * scale);

                Bitmap b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight,
        Bitmap b = null;
        if (addHeaderBitmap) {
            b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight,
                    Bitmap.Config.ARGB_8888);

            if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
                b.eraseColor(0xFFff0000);
            } else {
@@ -467,38 +584,22 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
                c.scale(tv.getScaleX(), tv.getScaleY());
                tv.mHeaderView.draw(c);
                c.setBitmap(null);

            }
            b = b.createAshmemBitmap();
        }

        int[] pts = new int[2];
        tv.getLocationOnScreen(pts);
                try {
                    WindowManagerGlobal.getWindowManagerService()
                            .overridePendingAppTransitionAspectScaledThumb(b,
                                    pts[0] + offsetX,
                                    pts[1] + offsetY,
                                    transform.rect.width(),
                                    transform.rect.height(),
                                    new IRemoteCallback.Stub() {
                                        @Override
                                        public void sendResult(Bundle data)
                                                throws RemoteException {
                                            post(new Runnable() {
                                                @Override
                                                public void run() {
                                                    if (animStartedListener != null) {
                                                        animStartedListener.onAnimationStarted();
                                                    }
                                                }
                                            });
                                        }
                                    }, true);
                } catch (RemoteException e) {
                    Log.w(TAG, "Error overriding app transition", e);
                }
            }
        };
        mCb.runAfterPause(r);

        final int left = pts[0] + offsetX;
        final int top = pts[1] + offsetY;
        final Rect rect = new Rect(left, top, left + transform.rect.width(),
                top + transform.rect.height());

        return new AppTransitionAnimationSpec(taskId, b, rect);
    }

    /**** TaskStackView.TaskStackCallbacks Implementation ****/

    @Override
@@ -521,12 +622,10 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
            // and then offset to the expected transform rect, but bound this to just
            // outside the display rect (to ensure we don't animate from too far away)
            sourceView = stackView;
            transform = stackView.getStackAlgorithm().getStackTransform(task, stackScroll, transform, null);
            offsetX = transform.rect.left;
            offsetY = mConfig.displayRect.height();
        } else {
            sourceView = tv.mThumbnailView;
            transform = stackView.getStackAlgorithm().getStackTransform(task, stackScroll, transform, null);
        }

        // Compute the thumbnail to scale up from
@@ -553,10 +652,8 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
                    }
                };
            }
            if (tv != null) {
                postDrawHeaderThumbnailTransitionRunnable(tv, offsetX, offsetY, transform,
            postDrawHeaderThumbnailTransitionRunnable(stackView, tv, offsetX, offsetY, stackScroll,
                    animStartedListener);
            }
            if (mConfig.multiStackEnabled) {
                opts = ActivityOptions.makeCustomAnimation(sourceView.getContext(),
                        R.anim.recents_from_unknown_enter,
+3 −3
Original line number Diff line number Diff line
@@ -122,7 +122,7 @@ public class StatusBarController extends BarController {
     *
     * @return the desired start time of the status bar transition, in uptime millis
     */
    private long calculateStatusBarTransitionStartTime(Animation openAnimation,
    private static long calculateStatusBarTransitionStartTime(Animation openAnimation,
            Animation closeAnimation) {
        if (openAnimation != null && closeAnimation != null) {
            TranslateAnimation openTranslateAnimation = findTranslateAnimation(openAnimation);
@@ -151,7 +151,7 @@ public class StatusBarController extends BarController {
     *
     * @return the found animation, {@code null} otherwise
     */
    private TranslateAnimation findTranslateAnimation(Animation animation) {
    private static TranslateAnimation findTranslateAnimation(Animation animation) {
        if (animation instanceof TranslateAnimation) {
            return (TranslateAnimation) animation;
        } else if (animation instanceof AnimationSet) {
@@ -170,7 +170,7 @@ public class StatusBarController extends BarController {
     * Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which
     * {@code interpolator(t + eps) > 0.99}.
     */
    private float findAlmostThereFraction(Interpolator interpolator) {
    private static float findAlmostThereFraction(Interpolator interpolator) {
        float val = 0.5f;
        float adj = 0.25f;
        while (adj >= 0.01f) {
Loading