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

Commit 1e37f551 authored by Garfield Tan's avatar Garfield Tan
Browse files

Add FreeformTaskTransitionHandler

This change ties the window decoration lifecycle to shell transitions if
it's enabled, so that we can leverage the new transition handler to hand
over window decorations.

This change exposes the transfers of management of tasks to task
listeners to implement maximizing. Eventually I want to use it to
implement the passover of window decorations across listeners.

This change lets freeform code handle maximizing transitions because
there isn't anything special to do for fullscreen tasks with window
decorations so we can keep FullscreenTaskListener simple.

Currently all animations related to freeform tasks are jumpcuts because
there isn't any spec defined.

Bug: 205580520
Test: Open, close, maximize freeform tasks with the shell transition and
the caption in shell enabled.
Test: atest WindowDecorationTests

Change-Id: Ie7bb92e0b5946b006fa685f4438815627c72dffe
parent 5a409f7a
Loading
Loading
Loading
Loading
+10 −10
Original line number Diff line number Diff line
@@ -29,8 +29,6 @@ import com.android.internal.logging.UiEventLogger;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.TaskViewFactoryController;
@@ -59,7 +57,7 @@ import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
@@ -78,7 +76,9 @@ import com.android.wm.shell.startingsurface.StartingSurface;
import com.android.wm.shell.startingsurface.StartingWindowController;
import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
import com.android.wm.shell.startingsurface.phone.PhoneStartingWindowTypeAlgorithm;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.sysui.ShellInterface;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
@@ -339,15 +339,15 @@ public abstract class WMShellBaseModule {
    // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
    @BindsOptionalOf
    @DynamicOverride
    abstract FreeformTaskListener<?> optionalFreeformTaskListener();
    abstract FreeformComponents optionalFreeformComponents();

    @WMSingleton
    @Provides
    static Optional<FreeformTaskListener<?>> provideFreeformTaskListener(
            @DynamicOverride Optional<FreeformTaskListener<?>> freeformTaskListener,
    static Optional<FreeformComponents> provideFreeformTaskListener(
            @DynamicOverride Optional<FreeformComponents> freeformComponents,
            Context context) {
        if (FreeformTaskListener.isFreeformEnabled(context)) {
            return freeformTaskListener;
        if (FreeformComponents.isFreeformEnabled(context)) {
            return freeformComponents;
        }
        return Optional.empty();
    }
@@ -636,7 +636,7 @@ public abstract class WMShellBaseModule {
            FullscreenTaskListener fullscreenTaskListener,
            Optional<UnfoldAnimationController> unfoldAnimationController,
            Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
            Optional<FreeformTaskListener<?>> freeformTaskListener,
            Optional<FreeformComponents> freeformComponents,
            Optional<RecentTasksController> recentTasksOptional,
            Optional<ActivityEmbeddingController> activityEmbeddingOptional,
            Transitions transitions,
@@ -655,7 +655,7 @@ public abstract class WMShellBaseModule {
                fullscreenTaskListener,
                unfoldAnimationController,
                unfoldTransitionHandler,
                freeformTaskListener,
                freeformComponents,
                recentTasksOptional,
                activityEmbeddingOptional,
                transitions,
+21 −2
Original line number Diff line number Diff line
@@ -49,7 +49,9 @@ import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
@@ -194,10 +196,27 @@ public abstract class WMShellModule {
    @WMSingleton
    @Provides
    @DynamicOverride
    static FreeformComponents provideFreeformComponents(
            FreeformTaskListener<?> taskListener,
            FreeformTaskTransitionHandler transitionHandler) {
        return new FreeformComponents(taskListener, Optional.of(transitionHandler));
    }

    @WMSingleton
    @Provides
    static FreeformTaskListener<?> provideFreeformTaskListener(
            WindowDecorViewModel<?> windowDecorViewModel) {
        return new FreeformTaskListener<>(windowDecorViewModel);
    }

    @WMSingleton
    @Provides
    static FreeformTaskTransitionHandler provideTaskTransitionHandler(
            Transitions transitions,
            WindowDecorViewModel<?> windowDecorViewModel,
            SyncTransactionQueue syncQueue) {
        return new FreeformTaskListener<>(windowDecorViewModel, syncQueue);
            FreeformTaskListener<?> freeformTaskListener) {
        return new FreeformTaskTransitionHandler(transitions, windowDecorViewModel,
                freeformTaskListener);
    }

    //
+56 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.freeform;

import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;

import android.content.Context;
import android.provider.Settings;

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

import java.util.Optional;

/**
 * Class that holds freeform related classes. It serves as the single injection point of
 * all freeform classes to avoid leaking implementation details to the base Dagger module.
 */
public class FreeformComponents {
    public final ShellTaskOrganizer.TaskListener mTaskListener;
    public final Optional<Transitions.TransitionHandler> mTaskTransitionHandler;

    /**
     * Creates an instance with the given components.
     */
    public FreeformComponents(
            ShellTaskOrganizer.TaskListener taskListener,
            Optional<Transitions.TransitionHandler> taskTransitionHandler) {
        mTaskListener = taskListener;
        mTaskTransitionHandler = taskTransitionHandler;
    }

    /**
     * Returns if this device supports freeform.
     */
    public static boolean isFreeformEnabled(Context context) {
        return context.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)
                || Settings.Global.getInt(context.getContentResolver(),
                DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
    }
}
+136 −42
Original line number Diff line number Diff line
@@ -16,20 +16,18 @@

package com.android.wm.shell.freeform;

import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;

import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.provider.Settings;
import android.util.Slog;
import android.util.Log;
import android.util.SparseArray;
import android.view.SurfaceControl;
import android.window.TransitionInfo;

import androidx.annotation.Nullable;

import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;

import java.io.PrintWriter;
@@ -45,9 +43,9 @@ public class FreeformTaskListener<T extends AutoCloseable>
    private static final String TAG = "FreeformTaskListener";

    private final WindowDecorViewModel<T> mWindowDecorationViewModel;
    private final SyncTransactionQueue mSyncQueue;

    private final SparseArray<State<T>> mTasks = new SparseArray<>();
    private final SparseArray<T> mWindowDecorOfVanishedTasks = new SparseArray<>();

    private static class State<T extends AutoCloseable> {
        RunningTaskInfo mTaskInfo;
@@ -55,58 +53,79 @@ public class FreeformTaskListener<T extends AutoCloseable>
        T mWindowDecoration;
    }

    public FreeformTaskListener(
            WindowDecorViewModel<T> windowDecorationViewModel,
            SyncTransactionQueue syncQueue) {
    public FreeformTaskListener(WindowDecorViewModel<T> windowDecorationViewModel) {
        mWindowDecorationViewModel = windowDecorationViewModel;
        mSyncQueue = syncQueue;
    }

    @Override
    public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
        if (mTasks.get(taskInfo.taskId) != null) {
            throw new RuntimeException("Task appeared more than once: #" + taskInfo.taskId);
        }
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Appeared: #%d",
                taskInfo.taskId);
        final State<T> state = new State<>();
        final State<T> state = createOrUpdateTaskState(taskInfo, leash);
        if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
            SurfaceControl.Transaction t = new SurfaceControl.Transaction();
            state.mWindowDecoration =
                    mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash, t, t);
            t.apply();
        }
    }

    private State<T> createOrUpdateTaskState(RunningTaskInfo taskInfo, SurfaceControl leash) {
        State<T> state = mTasks.get(taskInfo.taskId);
        if (state != null) {
            updateTaskInfo(taskInfo);
            return state;
        }

        state = new State<>();
        state.mTaskInfo = taskInfo;
        state.mLeash = leash;
        state.mWindowDecoration =
                mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash);
        mTasks.put(taskInfo.taskId, state);

        return state;
    }

    @Override
    public void onTaskVanished(RunningTaskInfo taskInfo) {
        State<T> state = mTasks.get(taskInfo.taskId);
        final State<T> state = mTasks.get(taskInfo.taskId);
        if (state == null) {
            Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId);
            // This is possible if the transition happens before this method.
            return;
        }
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Vanished: #%d",
                taskInfo.taskId);
        mTasks.remove(taskInfo.taskId);

        try {
            state.mWindowDecoration.close();
        } catch (Exception e) {
            Slog.e(TAG, "Failed to release window decoration.", e);
        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
            // Save window decorations of closing tasks so that we can hand them over to the
            // transition system if this method happens before the transition. In case where the
            // transition didn't happen, it'd be cleared when the next transition finished.
            if (state.mWindowDecoration != null) {
                mWindowDecorOfVanishedTasks.put(taskInfo.taskId, state.mWindowDecoration);
            }
            return;
        }
        releaseWindowDecor(state.mWindowDecoration);
    }

    @Override
    public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
        State<T> state = mTasks.get(taskInfo.taskId);
        if (state == null) {
            throw new RuntimeException(
                    "Task info changed before appearing: #" + taskInfo.taskId);
        }
        final State<T> state = updateTaskInfo(taskInfo);
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Info Changed: #%d",
                taskInfo.taskId);
        state.mTaskInfo = taskInfo;
        if (state.mWindowDecoration != null) {
            mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo, state.mWindowDecoration);
        }
    }

    private State<T> updateTaskInfo(RunningTaskInfo taskInfo) {
        final State<T> state = mTasks.get(taskInfo.taskId);
        if (state == null) {
            throw new RuntimeException("Task info changed before appearing: #" + taskInfo.taskId);
        }
        state.mTaskInfo = taskInfo;
        return state;
    }

    @Override
    public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
@@ -126,6 +145,93 @@ public class FreeformTaskListener<T extends AutoCloseable>
        return mTasks.get(taskId).mLeash;
    }

    /**
     * Creates a window decoration for a transition.
     *
     * @param change the change of this task transition that needs to have the task layer as the
     *               leash
     * @return {@code true} if it adopts the window decoration; {@code false} otherwise
     */
    void createWindowDecoration(
            TransitionInfo.Change change,
            SurfaceControl.Transaction startT,
            SurfaceControl.Transaction finishT) {
        final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
        state.mWindowDecoration = mWindowDecorationViewModel.createWindowDecoration(
                state.mTaskInfo, state.mLeash, startT, finishT);
    }

    /**
     * Gives out the ownership of the task's window decoration. The given task is leaving (of has
     * left) this task listener. This is the transition system asking for the ownership.
     *
     * @param taskInfo the maximizing task
     * @return the window decor of the maximizing task if any
     */
    T giveWindowDecoration(
            RunningTaskInfo taskInfo,
            SurfaceControl.Transaction startT,
            SurfaceControl.Transaction finishT) {
        T windowDecor = mWindowDecorOfVanishedTasks.get(taskInfo.taskId);
        mWindowDecorOfVanishedTasks.remove(taskInfo.taskId);
        final State<T> state = mTasks.get(taskInfo.taskId);
        if (state != null) {
            windowDecor = windowDecor == null ? state.mWindowDecoration : windowDecor;
            state.mWindowDecoration = null;
        }
        mWindowDecorationViewModel.setupWindowDecorationForTransition(
                taskInfo, startT, finishT, windowDecor);
        return windowDecor;
    }

    /**
     * Adopt the incoming window decoration and lets the window decoration prepare for a transition.
     *
     * @param change the change of this task transition that needs to have the task layer as the
     *               leash
     * @param startT the start transaction of this transition
     * @param finishT the finish transaction of this transition
     * @param windowDecor the window decoration to adopt
     * @return {@code true} if it adopts the window decoration; {@code false} otherwise
     */
    boolean adoptWindowDecoration(
            TransitionInfo.Change change,
            SurfaceControl.Transaction startT,
            SurfaceControl.Transaction finishT,
            @Nullable AutoCloseable windowDecor) {
        final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
        state.mWindowDecoration = mWindowDecorationViewModel.adoptWindowDecoration(windowDecor);
        if (state.mWindowDecoration != null) {
            mWindowDecorationViewModel.setupWindowDecorationForTransition(
                    state.mTaskInfo, startT, finishT, state.mWindowDecoration);
            return true;
        } else {
            state.mWindowDecoration = mWindowDecorationViewModel.createWindowDecoration(
                    state.mTaskInfo, state.mLeash, startT, finishT);
            return false;
        }
    }

    void onTaskTransitionFinished() {
        if (mWindowDecorOfVanishedTasks.size() == 0) {
            return;
        }
        Log.w(TAG, "Clearing window decors of vanished tasks. There could be visual defects "
                + "if any of them is used later in transitions.");
        for (int i = 0; i < mWindowDecorOfVanishedTasks.size(); ++i) {
            releaseWindowDecor(mWindowDecorOfVanishedTasks.valueAt(i));
        }
        mWindowDecorOfVanishedTasks.clear();
    }

    private void releaseWindowDecor(T windowDecor) {
        try {
            windowDecor.close();
        } catch (Exception e) {
            Log.e(TAG, "Failed to release window decoration.", e);
        }
    }

    @Override
    public void dump(PrintWriter pw, String prefix) {
        final String innerPrefix = prefix + "  ";
@@ -137,16 +243,4 @@ public class FreeformTaskListener<T extends AutoCloseable>
    public String toString() {
        return TAG;
    }

    /**
     * Checks if freeform support is enabled in system.
     *
     * @param context context used to check settings and package manager.
     * @return {@code true} if freeform is enabled, {@code false} if not.
     */
    public static boolean isFreeformEnabled(Context context) {
        return context.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)
                || Settings.Global.getInt(context.getContentResolver(),
                DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
    }
}
+234 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading