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

Commit 5959b4cd authored by Garfield Tan's avatar Garfield Tan
Browse files

Split window decor lifecycle to a TransitionObserver

There are several transitions involving window decorations that are
handled by Launcher, but we still need to tie window decorations to the
lifecycle of such transitions to release window decorations at the right
time. Thus we move this part of logic into a new class.

Bug: 241975249
Test: atest FreeformTaskTransitionObserverTest
Change-Id: Iff14be6f1fcb0b8ee7f634d5d41567348fff5c06
parent 0626b324
Loading
Loading
Loading
Loading
+16 −10
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ 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.freeform.FreeformTaskTransitionObserver;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
@@ -207,8 +208,10 @@ public abstract class WMShellModule {
    @DynamicOverride
    static FreeformComponents provideFreeformComponents(
            FreeformTaskListener<?> taskListener,
            FreeformTaskTransitionHandler transitionHandler) {
        return new FreeformComponents(taskListener, Optional.of(transitionHandler));
            FreeformTaskTransitionHandler transitionHandler,
            FreeformTaskTransitionObserver transitionObserver) {
        return new FreeformComponents(
                taskListener, Optional.of(transitionHandler), Optional.of(transitionObserver));
    }

    @WMSingleton
@@ -230,19 +233,22 @@ public abstract class WMShellModule {
    @WMSingleton
    @Provides
    static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler(
            ShellInit shellInit,
            Transitions transitions,
            WindowDecorViewModel<?> windowDecorViewModel) {
        return new FreeformTaskTransitionHandler(shellInit, transitions, windowDecorViewModel);
    }

    @WMSingleton
    @Provides
    static FreeformTaskTransitionObserver provideFreeformTaskTransitionObserver(
            Context context,
            ShellInit shellInit,
            Transitions transitions,
            WindowDecorViewModel<?> windowDecorViewModel,
            FullscreenTaskListener<?> fullscreenTaskListener,
            FreeformTaskListener<?> freeformTaskListener) {
        // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
        //                    override for this controller from the base module
        ShellInit init = FreeformComponents.isFreeformEnabled(context)
                ? shellInit
                : null;
        return new FreeformTaskTransitionHandler(init, transitions,
                windowDecorViewModel, fullscreenTaskListener, freeformTaskListener);
        return new FreeformTaskTransitionObserver(
                context, shellInit, transitions, fullscreenTaskListener, freeformTaskListener);
    }

    //
+6 −3
Original line number Diff line number Diff line
@@ -33,16 +33,19 @@ import java.util.Optional;
 */
public class FreeformComponents {
    public final ShellTaskOrganizer.TaskListener mTaskListener;
    public final Optional<Transitions.TransitionHandler> mTaskTransitionHandler;
    public final Optional<Transitions.TransitionHandler> mTransitionHandler;
    public final Optional<Transitions.TransitionObserver> mTransitionObserver;

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

    /**
+13 −129
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
import android.os.IBinder;
import android.util.Log;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
@@ -32,7 +31,6 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -41,17 +39,13 @@ import java.util.ArrayList;
import java.util.List;

/**
 * The {@link Transitions.TransitionHandler} that handles freeform task launches, closes,
 * maximizing and restoring transitions. It also reports transitions so that window decorations can
 * be a part of transitions.
 * The {@link Transitions.TransitionHandler} that handles freeform task maximizing and restoring
 * transitions.
 */
public class FreeformTaskTransitionHandler
        implements Transitions.TransitionHandler, FreeformTaskTransitionStarter {
    private static final String TAG = "FreeformTH";

    private final Transitions mTransitions;
    private final FreeformTaskListener<?> mFreeformTaskListener;
    private final FullscreenTaskListener<?> mFullscreenTaskListener;
    private final WindowDecorViewModel<?> mWindowDecorViewModel;

    private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
@@ -59,21 +53,16 @@ public class FreeformTaskTransitionHandler
    public FreeformTaskTransitionHandler(
            ShellInit shellInit,
            Transitions transitions,
            WindowDecorViewModel<?> windowDecorViewModel,
            FullscreenTaskListener<?> fullscreenTaskListener,
            FreeformTaskListener<?> freeformTaskListener) {
            WindowDecorViewModel<?> windowDecorViewModel) {
        mTransitions = transitions;
        mFullscreenTaskListener = fullscreenTaskListener;
        mFreeformTaskListener = freeformTaskListener;
        mWindowDecorViewModel = windowDecorViewModel;
        if (shellInit != null && Transitions.ENABLE_SHELL_TRANSITIONS) {
        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
            shellInit.addInitCallback(this::onInit, this);
        }
    }

    private void onInit() {
        mWindowDecorViewModel.setFreeformTaskTransitionStarter(this);
        mTransitions.addHandler(this);
    }

    @Override
@@ -101,13 +90,13 @@ public class FreeformTaskTransitionHandler
        mPendingTransitionTokens.add(mTransitions.startTransition(type, wct, this));
    }


    @Override
    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction startT,
            @NonNull SurfaceControl.Transaction finishT,
            @NonNull Transitions.TransitionFinishCallback finishCallback) {
        boolean transitionHandled = false;
        final ArrayList<AutoCloseable> windowDecorsInCloseTransitions = new ArrayList<>();
        for (TransitionInfo.Change change : info.getChanges()) {
            if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
                continue;
@@ -119,22 +108,13 @@ public class FreeformTaskTransitionHandler
            }

            switch (change.getMode()) {
                case WindowManager.TRANSIT_OPEN:
                    transitionHandled |= startOpenTransition(change, startT, finishT);
                    break;
                case WindowManager.TRANSIT_CLOSE:
                    transitionHandled |= startCloseTransition(
                            change, windowDecorsInCloseTransitions, startT, finishT);
                    break;
                case WindowManager.TRANSIT_CHANGE:
                    transitionHandled |= startChangeTransition(
                            transition, info.getType(), change, startT, finishT);
                            transition, info.getType(), change);
                    break;
                case WindowManager.TRANSIT_TO_BACK:
                    transitionHandled |= startMinimizeTransition(transition);
                    break;
                case WindowManager.TRANSIT_TO_FRONT:
                    break;
            }
        }

@@ -146,139 +126,43 @@ public class FreeformTaskTransitionHandler

        startT.apply();
        mTransitions.getMainExecutor().execute(
                () -> finishTransition(windowDecorsInCloseTransitions, finishCallback));
        return true;
    }

    private boolean startOpenTransition(
            TransitionInfo.Change change,
            SurfaceControl.Transaction startT,
            SurfaceControl.Transaction finishT) {
        switch (change.getTaskInfo().getWindowingMode()){
            case WINDOWING_MODE_FREEFORM:
                mFreeformTaskListener.createWindowDecoration(change, startT, finishT);
                break;
            case WINDOWING_MODE_FULLSCREEN:
                mFullscreenTaskListener.createWindowDecoration(change, startT, finishT);
                break;
            default:
                return false;
        }

        // Intercepted transition to manage the window decorations. Let other handlers animate.
        return false;
    }

    private boolean startCloseTransition(
            TransitionInfo.Change change,
            ArrayList<AutoCloseable> windowDecors,
            SurfaceControl.Transaction startT,
            SurfaceControl.Transaction finishT) {
        final AutoCloseable windowDecor;
        switch (change.getTaskInfo().getWindowingMode()) {
            case WINDOWING_MODE_FREEFORM:
                windowDecor = mFreeformTaskListener.giveWindowDecoration(change.getTaskInfo(),
                        startT, finishT);
                break;
            case WINDOWING_MODE_FULLSCREEN:
                windowDecor = mFullscreenTaskListener.giveWindowDecoration(change.getTaskInfo(),
                        startT, finishT);
                break;
            default:
                windowDecor = null;
        }
        if (windowDecor != null) {
            windowDecors.add(windowDecor);
        }
        // Intercepted transition to manage the window decorations. Let other handlers animate.
        return false;
    }

    private boolean startMinimizeTransition(IBinder transition) {
        if (!mPendingTransitionTokens.contains(transition)) {
            return false;
        }
                () -> finishCallback.onTransitionFinished(null, null));
        return true;
    }

    private boolean startChangeTransition(
            IBinder transition,
            int type,
            TransitionInfo.Change change,
            SurfaceControl.Transaction startT,
            SurfaceControl.Transaction finishT) {
        AutoCloseable windowDecor = null;

            TransitionInfo.Change change) {
        if (!mPendingTransitionTokens.contains(transition)) {
            return false;
        }

        boolean handled = false;
        boolean adopted = false;
        final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
        if (type == Transitions.TRANSIT_MAXIMIZE
                && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
            // TODO: Add maximize animations
            handled = true;
            windowDecor = mFreeformTaskListener.giveWindowDecoration(
                    change.getTaskInfo(), startT, finishT);
            adopted = mFullscreenTaskListener.adoptWindowDecoration(change,
                    startT, finishT, windowDecor);
        }

        if (type == Transitions.TRANSIT_RESTORE_FROM_MAXIMIZE
                && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
            // TODO: Add restore animations
            handled = true;
            windowDecor = mFullscreenTaskListener.giveWindowDecoration(
                    change.getTaskInfo(), startT, finishT);
            adopted = mFreeformTaskListener.adoptWindowDecoration(change,
                    startT, finishT, windowDecor);
        }

        if (!adopted) {
            releaseWindowDecor(windowDecor);
        }
        return handled;
    }

    private void finishTransition(
            ArrayList<AutoCloseable> windowDecorsInCloseTransitions,
            Transitions.TransitionFinishCallback finishCallback) {
        for (AutoCloseable windowDecor : windowDecorsInCloseTransitions) {
            releaseWindowDecor(windowDecor);
        }
        mFreeformTaskListener.onTaskTransitionFinished();
        mFullscreenTaskListener.onTaskTransitionFinished();
        finishCallback.onTransitionFinished(null, null);
    }

    private void releaseWindowDecor(AutoCloseable windowDecor) {
        if (windowDecor == null) {
            return;
        }
        try {
            windowDecor.close();
        } catch (Exception e) {
            Log.e(TAG, "Failed to release window decoration.", e);
        }
    private boolean startMinimizeTransition(IBinder transition) {
        return mPendingTransitionTokens.contains(transition);
    }

    @Nullable
    @Override
    public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
            @NonNull TransitionRequestInfo request) {
        final ActivityManager.RunningTaskInfo taskInfo = request.getTriggerTask();
        if (taskInfo == null || taskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
        return null;
    }
        switch (request.getType()) {
            case WindowManager.TRANSIT_OPEN:
            case WindowManager.TRANSIT_CLOSE:
            case WindowManager.TRANSIT_TO_FRONT:
            case WindowManager.TRANSIT_TO_BACK:
                return new WindowContainerTransaction();
            default:
                return null;
        }
    }

}
+222 −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.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;

import android.app.ActivityManager;
import android.content.Context;
import android.os.IBinder;
import android.util.Log;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * The {@link Transitions.TransitionHandler} that handles freeform task launches, closes,
 * maximizing and restoring transitions. It also reports transitions so that window decorations can
 * be a part of transitions.
 */
public class FreeformTaskTransitionObserver implements Transitions.TransitionObserver {
    private static final String TAG = "FreeformTO";

    private final Transitions mTransitions;
    private final FreeformTaskListener<?> mFreeformTaskListener;
    private final FullscreenTaskListener<?> mFullscreenTaskListener;

    private final Map<IBinder, List<AutoCloseable>> mTransitionToWindowDecors = new HashMap<>();

    public FreeformTaskTransitionObserver(
            Context context,
            ShellInit shellInit,
            Transitions transitions,
            FullscreenTaskListener<?> fullscreenTaskListener,
            FreeformTaskListener<?> freeformTaskListener) {
        mTransitions = transitions;
        mFreeformTaskListener = freeformTaskListener;
        mFullscreenTaskListener = fullscreenTaskListener;
        if (Transitions.ENABLE_SHELL_TRANSITIONS && FreeformComponents.isFreeformEnabled(context)) {
            shellInit.addInitCallback(this::onInit, this);
        }
    }

    @VisibleForTesting
    void onInit() {
        mTransitions.registerObserver(this);
    }

    @Override
    public void onTransitionReady(
            @NonNull IBinder transition,
            @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction startT,
            @NonNull SurfaceControl.Transaction finishT) {
        final ArrayList<AutoCloseable> windowDecors = new ArrayList<>();
        for (TransitionInfo.Change change : info.getChanges()) {
            if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
                continue;
            }

            final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
            if (taskInfo == null || taskInfo.taskId == -1) {
                continue;
            }

            switch (change.getMode()) {
                case WindowManager.TRANSIT_OPEN:
                    onOpenTransitionReady(change, startT, finishT);
                    break;
                case WindowManager.TRANSIT_CLOSE: {
                    onCloseTransitionReady(change, windowDecors, startT, finishT);
                    break;
                }
                case WindowManager.TRANSIT_CHANGE:
                    onChangeTransitionReady(info.getType(), change, startT, finishT);
                    break;
            }
        }
        if (!windowDecors.isEmpty()) {
            mTransitionToWindowDecors.put(transition, windowDecors);
        }
    }

    private void onOpenTransitionReady(
            TransitionInfo.Change change,
            SurfaceControl.Transaction startT,
            SurfaceControl.Transaction finishT) {
        switch (change.getTaskInfo().getWindowingMode()){
            case WINDOWING_MODE_FREEFORM:
                mFreeformTaskListener.createWindowDecoration(change, startT, finishT);
                break;
            case WINDOWING_MODE_FULLSCREEN:
                mFullscreenTaskListener.createWindowDecoration(change, startT, finishT);
                break;
        }
    }

    private void onCloseTransitionReady(
            TransitionInfo.Change change,
            ArrayList<AutoCloseable> windowDecors,
            SurfaceControl.Transaction startT,
            SurfaceControl.Transaction finishT) {
        final AutoCloseable windowDecor;
        switch (change.getTaskInfo().getWindowingMode()) {
            case WINDOWING_MODE_FREEFORM:
                windowDecor = mFreeformTaskListener.giveWindowDecoration(change.getTaskInfo(),
                        startT, finishT);
                break;
            case WINDOWING_MODE_FULLSCREEN:
                windowDecor = mFullscreenTaskListener.giveWindowDecoration(change.getTaskInfo(),
                        startT, finishT);
                break;
            default:
                windowDecor = null;
        }
        if (windowDecor != null) {
            windowDecors.add(windowDecor);
        }
    }

    private void onChangeTransitionReady(
            int type,
            TransitionInfo.Change change,
            SurfaceControl.Transaction startT,
            SurfaceControl.Transaction finishT) {
        AutoCloseable windowDecor = null;

        boolean adopted = false;
        final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
        if (type == Transitions.TRANSIT_MAXIMIZE
                && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
            windowDecor = mFreeformTaskListener.giveWindowDecoration(
                    change.getTaskInfo(), startT, finishT);
            adopted = mFullscreenTaskListener.adoptWindowDecoration(
                    change, startT, finishT, windowDecor);
        }

        if (type == Transitions.TRANSIT_RESTORE_FROM_MAXIMIZE
                && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
            windowDecor = mFullscreenTaskListener.giveWindowDecoration(
                    change.getTaskInfo(), startT, finishT);
            adopted = mFreeformTaskListener.adoptWindowDecoration(
                    change, startT, finishT, windowDecor);
        }

        if (!adopted) {
            releaseWindowDecor(windowDecor);
        }
    }

    @Override
    public void onTransitionStarting(@NonNull IBinder transition) {}

    @Override
    public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
        final List<AutoCloseable> windowDecorsOfMerged = mTransitionToWindowDecors.get(merged);
        if (windowDecorsOfMerged == null) {
            // We are adding window decorations of the merged transition to them of the playing
            // transition so if there is none of them there is nothing to do.
            return;
        }
        mTransitionToWindowDecors.remove(merged);

        final List<AutoCloseable> windowDecorsOfPlaying = mTransitionToWindowDecors.get(playing);
        if (windowDecorsOfPlaying != null) {
            windowDecorsOfPlaying.addAll(windowDecorsOfMerged);
        } else {
            mTransitionToWindowDecors.put(playing, windowDecorsOfMerged);
        }
    }

    @Override
    public void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {
        final List<AutoCloseable> windowDecors = mTransitionToWindowDecors.getOrDefault(
                transition, Collections.emptyList());
        mTransitionToWindowDecors.remove(transition);

        for (AutoCloseable windowDecor : windowDecors) {
            releaseWindowDecor(windowDecor);
        }
        mFullscreenTaskListener.onTaskTransitionFinished();
        mFreeformTaskListener.onTaskTransitionFinished();
    }

    private static void releaseWindowDecor(AutoCloseable windowDecor) {
        if (windowDecor == null) {
            return;
        }
        try {
            windowDecor.close();
        } catch (Exception e) {
            Log.e(TAG, "Failed to release window decoration.", e);
        }
    }
}
+302 −0

File added.

Preview size limit exceeded, changes collapsed.