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

Commit 51323896 authored by Orhan Uysal's avatar Orhan Uysal
Browse files

Refactor back nav logic to its own class.

Then wire the class into FreeformTaskTransitionObserver. This guarantees
that the changes will be triggered before DesktopTaskChangeListener
updates DesktopRepository.

Bug: 410540195
Bug: 407480909
Flag: EXEMPT Refactor
Test: Existing unit tests are passing, manually verify that back
navigation works fine.

Change-Id: I983145a78417572169abaca04b4c69c8b844883a
parent 2715b6e3
Loading
Loading
Loading
Loading
+23 −5
Original line number Diff line number Diff line
@@ -92,6 +92,7 @@ import com.android.wm.shell.dagger.pip.PipModule;
import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
import com.android.wm.shell.desktopmode.DesktopBackNavTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopDisplayEventHandler;
import com.android.wm.shell.desktopmode.DesktopImeHandler;
import com.android.wm.shell.desktopmode.DesktopImmersiveController;
@@ -511,7 +512,8 @@ public abstract class WMShellModule {
            FocusTransitionObserver focusTransitionObserver,
            Optional<DesksTransitionObserver> desksTransitionObserver,
            DesktopState desktopState,
            Optional<DesktopImeHandler> desktopImeHandler) {
            Optional<DesktopImeHandler> desktopImeHandler,
            Optional<DesktopBackNavTransitionObserver> desktopBackNavTransitionObserver) {
        return new FreeformTaskTransitionObserver(
                shellInit,
                transitions,
@@ -521,7 +523,8 @@ public abstract class WMShellModule {
                focusTransitionObserver,
                desksTransitionObserver,
                desktopState,
                desktopImeHandler);
                desktopImeHandler,
                desktopBackNavTransitionObserver);
    }

    @WMSingleton
@@ -1333,12 +1336,10 @@ public abstract class WMShellModule {
    @WMSingleton
    @Provides
    static Optional<DesktopTasksTransitionObserver> provideDesktopTasksTransitionObserver(
            Context context,
            Optional<DesktopUserRepositories> desktopUserRepositories,
            Transitions transitions,
            ShellTaskOrganizer shellTaskOrganizer,
            Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
            Optional<BackAnimationController> backAnimationController,
            DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider,
            DesktopState desktopState,
            ShellInit shellInit) {
@@ -1350,12 +1351,29 @@ public abstract class WMShellModule {
                                        transitions,
                                        shellTaskOrganizer,
                                        desktopMixedTransitionHandler.get(),
                                        backAnimationController.get(),
                                        desktopWallpaperActivityTokenProvider,
                                        desktopState,
                                        shellInit)));
    }

    @WMSingleton
    @Provides
    static Optional<DesktopBackNavTransitionObserver> provideDesktopBackNavTransitionObserver(
            Optional<DesktopUserRepositories> desktopUserRepositories,
            Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
            Optional<BackAnimationController> backAnimationController,
            DesktopState desktopState,
            ShellInit shellInit) {
        return desktopUserRepositories.flatMap(
                repository ->
                        Optional.of(
                                new DesktopBackNavTransitionObserver(
                                        repository,
                                        desktopMixedTransitionHandler.get(),
                                        backAnimationController.get(),
                                        desktopState,
                                        shellInit)));
    }
    @WMSingleton
    @Provides
    static Optional<DesksTransitionObserver> provideDesksTransitionObserver(
+178 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.desktopmode

import android.app.ActivityManager
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.os.IBinder
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.DesktopExperienceFlags
import android.window.DesktopModeFlags
import android.window.TransitionInfo
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.back.BackAnimationController
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.isExitDesktopModeTransition
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.desktopmode.DesktopState
import com.android.wm.shell.sysui.ShellInit

/**
 * Class responsible for updating [DesktopRepository] with back navigation related changes. Also
 * adds the back navigation transitions as [DesktopMixedTransitionHandler.PendingMixedTransition] to
 * the [DesktopMixedTransitionHandler].
 */
class DesktopBackNavTransitionObserver(
    private val desktopUserRepositories: DesktopUserRepositories,
    private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
    private val backAnimationController: BackAnimationController,
    desktopState: DesktopState,
    shellInit: ShellInit,
) {
    init {
        if (desktopState.canEnterDesktopMode) {
            shellInit.addInitCallback(::onInit, this)
        }
    }

    fun onInit() {
        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopBackNavigationTransitionObserver: onInit")
    }

    fun onTransitionReady(transition: IBinder, info: TransitionInfo) {
        if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
            handleBackNavigation(transition, info)
            removeTaskIfNeeded(info)
        }
    }

    private fun removeTaskIfNeeded(info: TransitionInfo) {
        // Since we are no longer removing all the tasks [onTaskVanished], we need to remove them by
        // checking the transitions.
        if (!(TransitionUtil.isOpeningType(info.type) || info.type.isExitDesktopModeTransition())) {
            return
        }
        // Remove a task from the repository if the app is launched outside of desktop.
        for (change in info.changes) {
            val taskInfo = change.taskInfo
            if (taskInfo == null || taskInfo.taskId == -1) continue

            val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
            if (
                desktopRepository.isActiveTask(taskInfo.taskId) &&
                    taskInfo.windowingMode != WINDOWING_MODE_FREEFORM
            ) {
                desktopRepository.removeTask(taskInfo.displayId, taskInfo.taskId)
            }
        }
    }

    private fun handleBackNavigation(transition: IBinder, info: TransitionInfo) {
        // When default back navigation happens, transition type is TO_BACK and the change is
        // TO_BACK. Mark the task going to back as minimized.
        if (info.type == TRANSIT_TO_BACK) {
            for (change in info.changes) {
                val taskInfo = change.taskInfo
                if (taskInfo == null || taskInfo.taskId == -1) {
                    continue
                }
                val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
                val isInDesktop = desktopRepository.isAnyDeskActive(taskInfo.displayId)
                if (
                    isInDesktop &&
                        change.mode == TRANSIT_TO_BACK &&
                        taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
                ) {
                    val isLastTask =
                        if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
                            desktopRepository.hasOnlyOneVisibleTask(taskInfo.displayId)
                        } else {
                            desktopRepository.isOnlyVisibleTask(taskInfo.taskId, taskInfo.displayId)
                        }
                    desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
                    desktopMixedTransitionHandler.addPendingMixedTransition(
                        DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
                            transition,
                            taskInfo.taskId,
                            isLastTask,
                        )
                    )
                }
            }
        } else if (info.type == TRANSIT_CLOSE) {
            // In some cases app will be closing as a result of back navigation but we would like
            // to minimize. Mark the task closing as minimized.
            var hasWallpaperClosing = false
            var minimizingTask: Int? = null
            for (change in info.changes) {
                val taskInfo = change.taskInfo
                if (taskInfo == null || taskInfo.taskId == -1) continue

                if (
                    TransitionUtil.isClosingMode(change.mode) &&
                        DesktopWallpaperActivity.isWallpaperTask(taskInfo)
                ) {
                    hasWallpaperClosing = true
                }

                if (change.mode == TRANSIT_CLOSE && minimizingTask == null) {
                    minimizingTask = getMinimizingTaskForClosingTransition(taskInfo)
                }
            }

            if (minimizingTask == null) return
            // If the transition has wallpaper closing, it means we are moving out of desktop.
            desktopMixedTransitionHandler.addPendingMixedTransition(
                DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
                    transition,
                    minimizingTask,
                    isLastTask = hasWallpaperClosing,
                )
            )
        }
    }

    /**
     * Given this a closing task in a closing transition, a task is assumed to be closed by back
     * navigation if:
     * 1) Desktop mode is visible.
     * 2) Task is in freeform.
     * 3) Task is the latest task that the back gesture is triggered on.
     * 4) It's not marked as a closing task as a result of closing it by the app header.
     *
     * This doesn't necessarily mean all the cases are because of back navigation but those cases
     * will be rare. E.g. triggering back navigation on an app that pops up a close dialog, and
     * closing it will minimize it here.
     */
    private fun getMinimizingTaskForClosingTransition(
        taskInfo: ActivityManager.RunningTaskInfo
    ): Int? {
        val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
        val isInDesktop = desktopRepository.isAnyDeskActive(taskInfo.displayId)
        if (
            isInDesktop &&
                taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
                backAnimationController.latestTriggerBackTask == taskInfo.taskId &&
                !desktopRepository.isClosingTask(taskInfo.taskId)
        ) {
            desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
            return taskInfo.taskId
        }
        return null
    }
}
+0 −123
Original line number Diff line number Diff line
@@ -31,11 +31,8 @@ import android.window.TransitionInfo
import android.window.WindowContainerTransaction
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.back.BackAnimationController
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.isExitDesktopModeTransition
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.TransitionUtil.isClosingMode
import com.android.wm.shell.shared.TransitionUtil.isOpeningMode
import com.android.wm.shell.shared.desktopmode.DesktopState
@@ -52,7 +49,6 @@ class DesktopTasksTransitionObserver(
    private val transitions: Transitions,
    private val shellTaskOrganizer: ShellTaskOrganizer,
    private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
    private val backAnimationController: BackAnimationController,
    private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
    desktopState: DesktopState,
    shellInit: ShellInit,
@@ -90,10 +86,6 @@ class DesktopTasksTransitionObserver(
            updateTopTransparentFullscreenTaskId(info)
        }
        updateWallpaperToken(info)
        if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
            handleBackNavigation(transition, info)
            removeTaskIfNeeded(info)
        }
        if (
            DesktopExperienceFlags.ENABLE_DESKTOP_CLOSE_TASK_ANIMATION_IN_DTC_BUGFIX.isTrue &&
                !desktopMixedTransitionHandler.hasTransition(transition) &&
@@ -106,27 +98,6 @@ class DesktopTasksTransitionObserver(
        removeWallpaperOnLastTaskClosingIfNeeded(transition, info)
    }

    private fun removeTaskIfNeeded(info: TransitionInfo) {
        // Since we are no longer removing all the tasks [onTaskVanished], we need to remove them by
        // checking the transitions.
        if (!(TransitionUtil.isOpeningType(info.type) || info.type.isExitDesktopModeTransition())) {
            return
        }
        // Remove a task from the repository if the app is launched outside of desktop.
        for (change in info.changes) {
            val taskInfo = change.taskInfo
            if (taskInfo == null || taskInfo.taskId == -1) continue

            val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
            if (
                desktopRepository.isActiveTask(taskInfo.taskId) &&
                    taskInfo.windowingMode != WINDOWING_MODE_FREEFORM
            ) {
                desktopRepository.removeTask(taskInfo.displayId, taskInfo.taskId)
            }
        }
    }

    private fun isCloseTransition(info: TransitionInfo): Boolean {
        for (change in info.changes) {
            val taskInfo = change.taskInfo
@@ -146,100 +117,6 @@ class DesktopTasksTransitionObserver(
        return false
    }

    private fun handleBackNavigation(transition: IBinder, info: TransitionInfo) {
        // When default back navigation happens, transition type is TO_BACK and the change is
        // TO_BACK. Mark the task going to back as minimized.
        if (info.type == TRANSIT_TO_BACK) {
            for (change in info.changes) {
                val taskInfo = change.taskInfo
                if (taskInfo == null || taskInfo.taskId == -1) {
                    continue
                }
                val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
                val isInDesktop = desktopRepository.isAnyDeskActive(taskInfo.displayId)
                if (
                    isInDesktop &&
                        change.mode == TRANSIT_TO_BACK &&
                        taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
                ) {
                    val isLastTask =
                        if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
                            desktopRepository.hasOnlyOneVisibleTask(taskInfo.displayId)
                        } else {
                            desktopRepository.isOnlyVisibleTask(taskInfo.taskId, taskInfo.displayId)
                        }
                    desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
                    desktopMixedTransitionHandler.addPendingMixedTransition(
                        DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
                            transition,
                            taskInfo.taskId,
                            isLastTask,
                        )
                    )
                }
            }
        } else if (info.type == TRANSIT_CLOSE) {
            // In some cases app will be closing as a result of back navigation but we would like
            // to minimize. Mark the task closing as minimized.
            var hasWallpaperClosing = false
            var minimizingTask: Int? = null
            for (change in info.changes) {
                val taskInfo = change.taskInfo
                if (taskInfo == null || taskInfo.taskId == -1) continue

                if (
                    TransitionUtil.isClosingMode(change.mode) &&
                        DesktopWallpaperActivity.isWallpaperTask(taskInfo)
                ) {
                    hasWallpaperClosing = true
                }

                if (change.mode == TRANSIT_CLOSE && minimizingTask == null) {
                    minimizingTask = getMinimizingTaskForClosingTransition(taskInfo)
                }
            }

            if (minimizingTask == null) return
            // If the transition has wallpaper closing, it means we are moving out of desktop.
            desktopMixedTransitionHandler.addPendingMixedTransition(
                DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
                    transition,
                    minimizingTask,
                    isLastTask = hasWallpaperClosing,
                )
            )
        }
    }

    /**
     * Given this a closing task in a closing transition, a task is assumed to be closed by back
     * navigation if:
     * 1) Desktop mode is visible.
     * 2) Task is in freeform.
     * 3) Task is the latest task that the back gesture is triggered on.
     * 4) It's not marked as a closing task as a result of closing it by the app header.
     *
     * This doesn't necessarily mean all the cases are because of back navigation but those cases
     * will be rare. E.g. triggering back navigation on an app that pops up a close dialog, and
     * closing it will minimize it here.
     */
    private fun getMinimizingTaskForClosingTransition(
        taskInfo: ActivityManager.RunningTaskInfo
    ): Int? {
        val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
        val isInDesktop = desktopRepository.isAnyDeskActive(taskInfo.displayId)
        if (
            isInDesktop &&
                taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
                backAnimationController.latestTriggerBackTask == taskInfo.taskId &&
                !desktopRepository.isClosingTask(taskInfo.taskId)
        ) {
            desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
            return taskInfo.taskId
        }
        return null
    }

    private fun removeWallpaperOnLastTaskClosingIfNeeded(
        transition: IBinder,
        info: TransitionInfo,
+6 −1
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import android.window.WindowContainerToken;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import com.android.wm.shell.desktopmode.DesktopBackNavTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopImeHandler;
import com.android.wm.shell.desktopmode.DesktopImmersiveController;
import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver;
@@ -58,6 +59,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
    private final FocusTransitionObserver mFocusTransitionObserver;
    private final Optional<DesksTransitionObserver> mDesksTransitionObserver;
    private final Optional<DesktopImeHandler> mDesktopImeHandler;
    private final Optional<DesktopBackNavTransitionObserver> mDesktopBackNavTransitionObserver;

    private final Map<IBinder, List<ActivityManager.RunningTaskInfo>> mTransitionToTaskInfo =
            new HashMap<>();
@@ -74,7 +76,8 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
            FocusTransitionObserver focusTransitionObserver,
            Optional<DesksTransitionObserver> desksTransitionObserver,
            DesktopState desktopState,
            Optional<DesktopImeHandler> desktopImeHandler) {
            Optional<DesktopImeHandler> desktopImeHandler,
            Optional<DesktopBackNavTransitionObserver> desktopBackNavTransitionObserver) {
        mTransitions = transitions;
        mDesktopImmersiveController = desktopImmersiveController;
        mWindowDecorViewModel = windowDecorViewModel;
@@ -82,6 +85,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
        mFocusTransitionObserver = focusTransitionObserver;
        mDesksTransitionObserver = desksTransitionObserver;
        mDesktopImeHandler = desktopImeHandler;
        mDesktopBackNavTransitionObserver = desktopBackNavTransitionObserver;
        if (FreeformComponents.requiresFreeformComponents(desktopState)) {
            shellInit.addInitCallback(this::onInit, this);
        }
@@ -115,6 +119,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs

        // Call after the focus state update to have the correct focused window.
        mDesktopImeHandler.ifPresent(o -> o.onTransitionReady(transition, info));
        mDesktopBackNavTransitionObserver.ifPresent(o ->  o.onTransitionReady(transition, info));

        final ArrayList<ActivityManager.RunningTaskInfo> taskInfoList = new ArrayList<>();
        final ArrayList<WindowContainerToken> taskParents = new ArrayList<>();
+298 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading