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

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

Merge "Hook-up TaskView/bubbles to shell transition"

parents c0fed624 927f9f5c
Loading
Loading
Loading
Loading
+148 −8
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;

import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.transition.Transitions;

import java.io.PrintWriter;
import java.util.concurrent.Executor;
@@ -77,6 +78,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
    private final ShellTaskOrganizer mTaskOrganizer;
    private final Executor mShellExecutor;
    private final SyncTransactionQueue mSyncQueue;
    private final TaskViewTransitions mTaskViewTransitions;

    private ActivityManager.RunningTaskInfo mTaskInfo;
    private WindowContainerToken mTaskToken;
@@ -92,17 +94,27 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
    private final Rect mTmpRootRect = new Rect();
    private final int[] mTmpLocation = new int[2];

    public TaskView(Context context, ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue) {
    public TaskView(Context context, ShellTaskOrganizer organizer,
            TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) {
        super(context, null, 0, 0, true /* disableBackgroundLayer */);

        mTaskOrganizer = organizer;
        mShellExecutor = organizer.getExecutor();
        mSyncQueue = syncQueue;
        mTaskViewTransitions = taskViewTransitions;
        if (mTaskViewTransitions != null) {
            mTaskViewTransitions.addTaskView(this);
        }
        setUseAlpha();
        getHolder().addCallback(this);
        mGuard.open("release");
    }

    /** Until all users are converted, we may have mixed-use (eg. Car). */
    private boolean isUsingShellTransitions() {
        return mTaskViewTransitions != null && Transitions.ENABLE_SHELL_TRANSITIONS;
    }

    /**
     * Only one listener may be set on the view, throws an exception otherwise.
     */
@@ -129,6 +141,14 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
            @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
        prepareActivityOptions(options, launchBounds);
        LauncherApps service = mContext.getSystemService(LauncherApps.class);
        if (isUsingShellTransitions()) {
            mShellExecutor.execute(() -> {
                final WindowContainerTransaction wct = new WindowContainerTransaction();
                wct.startShortcut(mContext.getPackageName(), shortcut, options.toBundle());
                mTaskViewTransitions.startTaskView(wct, this);
            });
            return;
        }
        try {
            service.startShortcut(shortcut, null /* sourceBounds */, options.toBundle());
        } catch (Exception e) {
@@ -148,6 +168,14 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
    public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
            @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
        prepareActivityOptions(options, launchBounds);
        if (isUsingShellTransitions()) {
            mShellExecutor.execute(() -> {
                WindowContainerTransaction wct = new WindowContainerTransaction();
                wct.sendPendingIntent(pendingIntent, fillInIntent, options.toBundle());
                mTaskViewTransitions.startTaskView(wct, this);
            });
            return;
        }
        try {
            pendingIntent.send(mContext, 0 /* code */, fillInIntent,
                    null /* onFinished */, null /* handler */, null /* requiredPermission */,
@@ -177,6 +205,16 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
        mObscuredTouchRect = obscuredRect;
    }

    private void onLocationChanged(WindowContainerTransaction wct) {
        // Update based on the screen bounds
        getBoundsOnScreen(mTmpRect);
        getRootView().getBoundsOnScreen(mTmpRootRect);
        if (!mTmpRootRect.contains(mTmpRect)) {
            mTmpRect.offsetTo(0, 0);
        }
        wct.setBounds(mTaskToken, mTmpRect);
    }

    /**
     * Call when view position or size has changed. Do not call when animating.
     */
@@ -184,15 +222,12 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
        if (mTaskToken == null) {
            return;
        }
        // Update based on the screen bounds
        getBoundsOnScreen(mTmpRect);
        getRootView().getBoundsOnScreen(mTmpRootRect);
        if (!mTmpRootRect.contains(mTmpRect)) {
            mTmpRect.offsetTo(0, 0);
        }
        // Sync Transactions can't operate simultaneously with shell transition collection.
        // The transition animation (upon showing) will sync the location itself.
        if (isUsingShellTransitions() && mTaskViewTransitions.hasPending()) return;

        WindowContainerTransaction wct = new WindowContainerTransaction();
        wct.setBounds(mTaskToken, mTmpRect);
        onLocationChanged(wct);
        mSyncQueue.queue(wct);
    }

@@ -217,6 +252,9 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,

    private void performRelease() {
        getHolder().removeCallback(this);
        if (mTaskViewTransitions != null) {
            mTaskViewTransitions.removeTaskView(this);
        }
        mShellExecutor.execute(() -> {
            mTaskOrganizer.removeListener(this);
            resetTaskInfo();
@@ -254,6 +292,10 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
    @Override
    public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
            SurfaceControl leash) {
        if (isUsingShellTransitions()) {
            // Everything else handled by enter transition.
            return;
        }
        mTaskInfo = taskInfo;
        mTaskToken = taskInfo.token;
        mTaskLeash = leash;
@@ -288,6 +330,8 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,

    @Override
    public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
        // Unlike Appeared, we can't yet guarantee that vanish will happen within a transition that
        // we know about -- so leave clean-up here even if shell transitions are enabled.
        if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;

        if (mListener != null) {
@@ -355,6 +399,10 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
                // Nothing to update, task is not yet available
                return;
            }
            if (isUsingShellTransitions()) {
                mTaskViewTransitions.setTaskViewVisible(this, true /* visible */);
                return;
            }
            // Reparent the task when this surface is created
            mTransaction.reparent(mTaskLeash, getSurfaceControl())
                    .show(mTaskLeash)
@@ -380,6 +428,11 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
                return;
            }

            if (isUsingShellTransitions()) {
                mTaskViewTransitions.setTaskViewVisible(this, false /* visible */);
                return;
            }

            // Unparent the task when this surface is destroyed
            mTransaction.reparent(mTaskLeash, null).apply();
            updateTaskVisibility();
@@ -421,4 +474,91 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
        super.onDetachedFromWindow();
        getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
    }

    ActivityManager.RunningTaskInfo getTaskInfo() {
        return mTaskInfo;
    }

    void prepareHideAnimation(@NonNull SurfaceControl.Transaction finishTransaction) {
        if (mTaskToken == null) {
            // Nothing to update, task is not yet available
            return;
        }

        finishTransaction.reparent(mTaskLeash, null).apply();

        if (mListener != null) {
            final int taskId = mTaskInfo.taskId;
            mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */);
        }
    }

    /**
     * Called when the associated Task closes. If the TaskView is just being hidden, prepareHide
     * is used instead.
     */
    void prepareCloseAnimation() {
        if (mTaskToken != null) {
            if (mListener != null) {
                final int taskId = mTaskInfo.taskId;
                mListenerExecutor.execute(() -> {
                    mListener.onTaskRemovalStarted(taskId);
                });
            }
            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
        }
        resetTaskInfo();
    }

    void prepareOpenAnimation(final boolean newTask,
            @NonNull SurfaceControl.Transaction startTransaction,
            @NonNull SurfaceControl.Transaction finishTransaction,
            ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
            WindowContainerTransaction wct) {
        mTaskInfo = taskInfo;
        mTaskToken = mTaskInfo.token;
        mTaskLeash = leash;
        if (mSurfaceCreated) {
            // Surface is ready, so just reparent the task to this surface control
            startTransaction.reparent(mTaskLeash, getSurfaceControl())
                    .show(mTaskLeash)
                    .apply();
            // Also reparent on finishTransaction since the finishTransaction will reparent back
            // to its "original" parent by default.
            finishTransaction.reparent(mTaskLeash, getSurfaceControl())
                    .setPosition(mTaskLeash, 0, 0)
                    .apply();

            // TODO: determine if this is really necessary or not
            onLocationChanged(wct);
        } else {
            // The surface has already been destroyed before the task has appeared,
            // so go ahead and hide the task entirely
            wct.setHidden(mTaskToken, true /* hidden */);
            // listener callback is below
        }
        if (newTask) {
            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true /* intercept */);
        }

        if (mTaskInfo.taskDescription != null) {
            int backgroundColor = mTaskInfo.taskDescription.getBackgroundColor();
            setResizeBackgroundColor(startTransaction, backgroundColor);
        }

        if (mListener != null) {
            final int taskId = mTaskInfo.taskId;
            final ComponentName baseActivity = mTaskInfo.baseActivity;

            mListenerExecutor.execute(() -> {
                if (newTask) {
                    mListener.onTaskCreated(taskId, baseActivity);
                }
                // Even if newTask, send a visibilityChange if the surface was destroyed.
                if (!newTask || !mSurfaceCreated) {
                    mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */);
                }
            });
        }
    }
}
+12 −1
Original line number Diff line number Diff line
@@ -31,13 +31,24 @@ public class TaskViewFactoryController {
    private final ShellTaskOrganizer mTaskOrganizer;
    private final ShellExecutor mShellExecutor;
    private final SyncTransactionQueue mSyncQueue;
    private final TaskViewTransitions mTaskViewTransitions;
    private final TaskViewFactory mImpl = new TaskViewFactoryImpl();

    public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
            ShellExecutor shellExecutor, SyncTransactionQueue syncQueue,
            TaskViewTransitions taskViewTransitions) {
        mTaskOrganizer = taskOrganizer;
        mShellExecutor = shellExecutor;
        mSyncQueue = syncQueue;
        mTaskViewTransitions = taskViewTransitions;
    }

    public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
            ShellExecutor shellExecutor, SyncTransactionQueue syncQueue) {
        mTaskOrganizer = taskOrganizer;
        mShellExecutor = shellExecutor;
        mSyncQueue = syncQueue;
        mTaskViewTransitions = null;
    }

    public TaskViewFactory asTaskViewFactory() {
@@ -46,7 +57,7 @@ public class TaskViewFactoryController {

    /** Creates an {@link TaskView} */
    public void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate) {
        TaskView taskView = new TaskView(context, mTaskOrganizer, mSyncQueue);
        TaskView taskView = new TaskView(context, mTaskOrganizer, mTaskViewTransitions, mSyncQueue);
        executor.execute(() -> {
            onCreate.accept(taskView);
        });
+253 −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 static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.os.IBinder;
import android.util.Slog;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;

import com.android.wm.shell.transition.Transitions;

import java.util.ArrayList;

/**
 * Handles Shell Transitions that involve TaskView tasks.
 */
public class TaskViewTransitions implements Transitions.TransitionHandler {
    private static final String TAG = "TaskViewTransitions";

    private final ArrayList<TaskView> mTaskViews = new ArrayList<>();
    private final ArrayList<PendingTransition> mPending = new ArrayList<>();
    private final Transitions mTransitions;
    private final boolean[] mRegistered = new boolean[]{ false };

    /**
     * TaskView makes heavy use of startTransition. Only one shell-initiated transition can be
     * in-flight (collecting) at a time (because otherwise, the operations could get merged into
     * a single transition). So, keep a queue here until we add a queue in server-side.
     */
    private static class PendingTransition {
        final @WindowManager.TransitionType int mType;
        final WindowContainerTransaction mWct;
        final @NonNull TaskView mTaskView;
        IBinder mClaimed;

        PendingTransition(@WindowManager.TransitionType int type,
                @Nullable WindowContainerTransaction wct, @NonNull TaskView taskView) {
            mType = type;
            mWct = wct;
            mTaskView = taskView;
        }
    }

    public TaskViewTransitions(Transitions transitions) {
        mTransitions = transitions;
        // Defer registration until the first TaskView because we want this to be the "first" in
        // priority when handling requests.
        // TODO(210041388): register here once we have an explicit ordering mechanism.
    }

    void addTaskView(TaskView tv) {
        synchronized (mRegistered) {
            if (!mRegistered[0]) {
                mRegistered[0] = true;
                mTransitions.addHandler(this);
            }
        }
        mTaskViews.add(tv);
    }

    void removeTaskView(TaskView tv) {
        mTaskViews.remove(tv);
        // Note: Don't unregister handler since this is a singleton with lifetime bound to Shell
    }

    /**
     * Looks through the pending transitions for one matching `taskView`.
     * @param taskView the pending transition should be for this.
     * @param closing When true, this only returns a pending transition of the close/hide type.
     *                Otherwise it selects open/show.
     * @param latest When true, this will only check the most-recent pending transition for the
     *               specified taskView. If it doesn't match `closing`, this will return null even
     *               if there is a match earlier. The idea behind this is to check the state of
     *               the taskviews "as if all transitions already happened".
     */
    private PendingTransition findPending(TaskView taskView, boolean closing, boolean latest) {
        for (int i = mPending.size() - 1; i >= 0; --i) {
            if (mPending.get(i).mTaskView != taskView) continue;
            if (Transitions.isClosingType(mPending.get(i).mType) == closing) {
                return mPending.get(i);
            }
            if (latest) {
                return null;
            }
        }
        return null;
    }

    private PendingTransition findPending(IBinder claimed) {
        for (int i = 0; i < mPending.size(); ++i) {
            if (mPending.get(i).mClaimed != claimed) continue;
            return mPending.get(i);
        }
        return null;
    }

    /** @return whether there are pending transitions on TaskViews. */
    public boolean hasPending() {
        return !mPending.isEmpty();
    }

    @Override
    public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
            @Nullable TransitionRequestInfo request) {
        final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
        if (triggerTask == null) {
            return null;
        }
        final TaskView taskView = findTaskView(triggerTask);
        if (taskView == null) return null;
        // Opening types should all be initiated by shell
        if (!Transitions.isClosingType(request.getType())) return null;
        PendingTransition pending = findPending(taskView, true /* closing */, false /* latest */);
        if (pending == null) {
            pending = new PendingTransition(request.getType(), null, taskView);
        }
        if (pending.mClaimed != null) {
            throw new IllegalStateException("Task is closing in 2 collecting transitions?"
                    + " This state doesn't make sense");
        }
        pending.mClaimed = transition;
        return new WindowContainerTransaction();
    }

    private TaskView findTaskView(ActivityManager.RunningTaskInfo taskInfo) {
        for (int i = 0; i < mTaskViews.size(); ++i) {
            if (mTaskViews.get(i).getTaskInfo() == null) continue;
            if (taskInfo.token.equals(mTaskViews.get(i).getTaskInfo().token)) {
                return mTaskViews.get(i);
            }
        }
        return null;
    }

    void startTaskView(WindowContainerTransaction wct, TaskView taskView) {
        mPending.add(new PendingTransition(TRANSIT_OPEN, wct, taskView));
        startNextTransition();
    }

    void setTaskViewVisible(TaskView taskView, boolean visible) {
        PendingTransition pending = findPending(taskView, !visible, true /* latest */);
        if (pending != null) {
            // Already opening or creating a task, so no need to do anything here.
            return;
        }
        if (taskView.getTaskInfo() == null) {
            // Nothing to update, task is not yet available
            return;
        }
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        wct.setHidden(taskView.getTaskInfo().token, !visible /* hidden */);
        pending = new PendingTransition(
                visible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK, wct, taskView);
        mPending.add(pending);
        startNextTransition();
        // visibility is reported in transition.
    }

    private void startNextTransition() {
        if (mPending.isEmpty()) return;
        final PendingTransition pending = mPending.get(0);
        if (pending.mClaimed != null) {
            // Wait for this to start animating.
            return;
        }
        pending.mClaimed = mTransitions.startTransition(pending.mType, pending.mWct, this);
    }

    @Override
    public boolean startAnimation(@NonNull IBinder transition,
            @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction startTransaction,
            @NonNull SurfaceControl.Transaction finishTransaction,
            @NonNull Transitions.TransitionFinishCallback finishCallback) {
        PendingTransition pending = findPending(transition);
        if (pending == null) return false;
        mPending.remove(pending);
        TaskView taskView = pending.mTaskView;
        final ArrayList<TransitionInfo.Change> tasks = new ArrayList<>();
        for (int i = 0; i < info.getChanges().size(); ++i) {
            final TransitionInfo.Change chg = info.getChanges().get(i);
            if (chg.getTaskInfo() == null) continue;
            tasks.add(chg);
        }
        if (tasks.isEmpty()) {
            Slog.e(TAG, "Got a TaskView transition with no task.");
            return false;
        }
        WindowContainerTransaction wct = null;
        for (int i = 0; i < tasks.size(); ++i) {
            TransitionInfo.Change chg = tasks.get(i);
            if (Transitions.isClosingType(chg.getMode())) {
                final boolean isHide = chg.getMode() == TRANSIT_TO_BACK;
                TaskView tv = findTaskView(chg.getTaskInfo());
                if (tv == null) {
                    throw new IllegalStateException("TaskView transition is closing a "
                            + "non-taskview task ");
                }
                if (isHide) {
                    tv.prepareHideAnimation(finishTransaction);
                } else {
                    tv.prepareCloseAnimation();
                }
            } else if (Transitions.isOpeningType(chg.getMode())) {
                final boolean taskIsNew = chg.getMode() == TRANSIT_OPEN;
                if (wct == null) wct = new WindowContainerTransaction();
                TaskView tv = taskView;
                if (!taskIsNew) {
                    tv = findTaskView(chg.getTaskInfo());
                    if (tv == null) {
                        throw new IllegalStateException("TaskView transition is showing a "
                            + "non-taskview task ");
                    }
                }
                tv.prepareOpenAnimation(taskIsNew, startTransaction, finishTransaction,
                        chg.getTaskInfo(), chg.getLeash(), wct);
            } else {
                throw new IllegalStateException("Claimed transition isn't an opening or closing"
                        + " type: " + chg.getMode());
            }
        }
        // No animation, just show it immediately.
        startTransaction.apply();
        finishTransaction.apply();
        finishCallback.onTransitionFinished(wct, null /* wctCB */);
        startNextTransition();
        return true;
    }
}
+10 −1
Original line number Diff line number Diff line
@@ -80,6 +80,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
@@ -136,6 +137,7 @@ public class BubbleController {
    private final TaskStackListenerImpl mTaskStackListener;
    private final ShellTaskOrganizer mTaskOrganizer;
    private final DisplayController mDisplayController;
    private final TaskViewTransitions mTaskViewTransitions;
    private final SyncTransactionQueue mSyncQueue;

    // Used to post to main UI thread
@@ -212,6 +214,7 @@ public class BubbleController {
            DisplayController displayController,
            ShellExecutor mainExecutor,
            Handler mainHandler,
            TaskViewTransitions taskViewTransitions,
            SyncTransactionQueue syncQueue) {
        BubbleLogger logger = new BubbleLogger(uiEventLogger);
        BubblePositioner positioner = new BubblePositioner(context, windowManager);
@@ -220,7 +223,7 @@ public class BubbleController {
                new BubbleDataRepository(context, launcherApps, mainExecutor),
                statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
                logger, taskStackListener, organizer, positioner, displayController, mainExecutor,
                mainHandler, syncQueue);
                mainHandler, taskViewTransitions, syncQueue);
    }

    /**
@@ -243,6 +246,7 @@ public class BubbleController {
            DisplayController displayController,
            ShellExecutor mainExecutor,
            Handler mainHandler,
            TaskViewTransitions taskViewTransitions,
            SyncTransactionQueue syncQueue) {
        mContext = context;
        mLauncherApps = launcherApps;
@@ -266,6 +270,7 @@ public class BubbleController {
        mSavedBubbleKeysPerUser = new SparseSetArray<>();
        mBubbleIconFactory = new BubbleIconFactory(context);
        mDisplayController = displayController;
        mTaskViewTransitions = taskViewTransitions;
        mSyncQueue = syncQueue;
    }

@@ -570,6 +575,10 @@ public class BubbleController {
        return mSyncQueue;
    }

    TaskViewTransitions getTaskViewTransitions() {
        return mTaskViewTransitions;
    }

    /** Contains information to help position things on the screen.  */
    BubblePositioner getPositioner() {
        return mBubblePositioner;
+1 −1
Original line number Diff line number Diff line
@@ -335,7 +335,7 @@ public class BubbleExpandedView extends LinearLayout {
            mManageButton.setVisibility(GONE);
        } else {
            mTaskView = new TaskView(mContext, mController.getTaskOrganizer(),
                    mController.getSyncTransactionQueue());
                    mController.getTaskViewTransitions(), mController.getSyncTransactionQueue());
            mTaskView.setListener(mController.getMainExecutor(), mTaskViewListener);
            mExpandedViewContainer.addView(mTaskView);
            bringChildToFront(mTaskView);
Loading