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

Commit 629004e7 authored by Omar Elmekkawy's avatar Omar Elmekkawy Committed by Android (Google) Code Review
Browse files

Merge "Resize windows on resolution or dpi changes in desktop windowing." into main

parents 2b278ab3 30e59f71
Loading
Loading
Loading
Loading
+37 −1
Original line number Diff line number Diff line
@@ -70,7 +70,9 @@ public class DisplayController {
    private final DesktopState mDesktopState;

    private final SparseArray<DisplayRecord> mDisplays = new SparseArray<>();

    private final ArrayList<OnDisplaysChangedListener> mDisplayChangedListeners = new ArrayList<>();

    private DisplayTopology mDisplayTopology;

    public DisplayController(Context context, IWindowManager wmService, ShellInit shellInit,
@@ -198,10 +200,27 @@ public class DisplayController {
     * Updates the insets for a given display.
     */
    public void updateDisplayInsets(int displayId, InsetsState state) {
        final Rect oldStableBounds = new Rect();
        final Rect newStableBounds = new Rect();
        final DisplayLayout oldDisplayLayout = getDisplayLayout(displayId);
        if (oldDisplayLayout != null) {
            oldDisplayLayout.getStableBounds(oldStableBounds);
        }
        final DisplayRecord r = mDisplays.get(displayId);
        if (r != null) {
            r.setInsets(state);
        }
        final DisplayLayout newDisplayLayout = getDisplayLayout(displayId);
        if (newDisplayLayout != null) {
            newDisplayLayout.getStableBounds(newStableBounds);
        }

        if (oldStableBounds != newStableBounds) {
            for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
                mDisplayChangedListeners.get(i).onStableInsetsChanging(
                        displayId, oldDisplayLayout);
            }
        }
    }

    /**
@@ -350,12 +369,13 @@ public class DisplayController {
            final Context perDisplayContext = (displayId == Display.DEFAULT_DISPLAY)
                    ? mContext
                    : mContext.createDisplayContext(display);
            DisplayLayout oldLayout = dr.mDisplayLayout;
            final Context context = perDisplayContext.createConfigurationContext(newConfig);
            final DisplayLayout displayLayout = dr.createLayout(context, display);
            dr.setDisplayLayout(context, displayLayout);
            for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
                mDisplayChangedListeners.get(i).onDisplayConfigurationChanged(
                        displayId, newConfig);
                        displayId, newConfig, oldLayout);
            }
        }
    }
@@ -586,6 +606,22 @@ public class DisplayController {
         */
        default void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {}

        /**
         * Called when a display's window-container configuration changes, includes old layout.
         */
        default void onDisplayConfigurationChanged(int displayId, Configuration newConfig,
                DisplayLayout oldLayout) {
            this.onDisplayConfigurationChanged(displayId, newConfig);
        }

        /**
         * Notifies listeners of a stable insets change.
         * This is usually called after a configuration change when the system components update
         * their bounds.
         * @param displayId display who's layout is changing.
         * @param oldLayout the layout of this display before the change is applied.
         */
        default void onStableInsetsChanging(int displayId, DisplayLayout oldLayout) {}
        /**
         * Called when a display is removed.
         */
+4 −2
Original line number Diff line number Diff line
@@ -1729,7 +1729,8 @@ public abstract class WMShellModule {
            Optional<DesktopDisplayModeController> desktopDisplayModeController,
            DesktopRepositoryInitializer desktopRepositoryInitializer,
            Optional<DesksTransitionObserver> desksTransitionObserver,
            DesktopState desktopState
            DesktopState desktopState,
            Transitions transitions
    ) {
        if (!desktopState.canEnterDesktopMode()) {
            return Optional.empty();
@@ -1747,7 +1748,8 @@ public abstract class WMShellModule {
                        desktopTasksController.get(),
                        desktopDisplayModeController.get(),
                        desksTransitionObserver.get(),
                        desktopState));
                        desktopState,
                        transitions));
    }

    @WMSingleton
+138 −1
Original line number Diff line number Diff line
@@ -17,16 +17,22 @@
package com.android.wm.shell.desktopmode

import android.content.Context
import android.content.res.Configuration
import android.graphics.Rect
import android.os.IBinder
import android.os.Trace
import android.os.UserHandle
import android.os.UserManager
import android.util.ArraySet
import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.window.DesktopExperienceFlags
import android.window.DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_ACTIVATION_IN_DESKTOP_FIRST_DISPLAYS
import android.window.DesktopModeFlags
import android.window.DisplayAreaInfo
import android.window.TransitionInfo
import com.android.app.tracing.traceSection
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.protolog.ProtoLog
@@ -34,6 +40,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
import com.android.wm.shell.desktopmode.data.DesktopRepository
import com.android.wm.shell.desktopmode.data.DesktopRepositoryInitializer
@@ -48,6 +55,7 @@ import com.android.wm.shell.shared.desktopmode.DesktopState
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.sysui.UserChangeListener
import com.android.wm.shell.transition.Transitions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
@@ -66,7 +74,12 @@ class DesktopDisplayEventHandler(
    private val desktopDisplayModeController: DesktopDisplayModeController,
    private val desksTransitionObserver: DesksTransitionObserver,
    private val desktopState: DesktopState,
) : OnDisplaysChangedListener, OnDeskRemovedListener, PreserveDisplayRequestHandler {
    private val transitions: Transitions,
) :
    OnDisplaysChangedListener,
    OnDeskRemovedListener,
    PreserveDisplayRequestHandler,
    Transitions.TransitionObserver {

    private val onDisplayAreaChangeListener = OnDisplayAreaChangeListener { displayId ->
        logV("displayAreaChanged in displayId=%d", displayId)
@@ -79,6 +92,10 @@ class DesktopDisplayEventHandler(
    // displayId to its uniqueId since we will not be able to fetch it after disconnect.
    private val uniqueIdByDisplayId = mutableMapOf<Int, String>()

    private val oldDpiLayoutByDisplayId = mutableMapOf<Int, DisplayLayout>()
    private val boundsChangedByDisplayId = mutableSetOf<Int>()
    private val stableBoundsChangedByDisplayId = mutableSetOf<Int>()
    private val displayConfigById = mutableMapOf<Int, Configuration>()
    // All uniqueDisplayIds that are currently being restored; any further requests
    // to restore them will no-op.
    @VisibleForTesting val displaysMidRestoration = ArraySet<String>()
@@ -107,6 +124,126 @@ class DesktopDisplayEventHandler(
        }
    }

    override fun onDisplayConfigurationChanged(
        displayId: Int,
        newConfig: Configuration?,
        oldLayout: DisplayLayout?,
    ) {
        val newDisplayLayout = displayController.getDisplayLayout(displayId)
        val oldDisplayLayout = oldDpiLayoutByDisplayId[displayId] ?: oldLayout
        if (oldDisplayLayout == null || newDisplayLayout == null) return
        newConfig?.let { displayConfigById.put(displayId, it) }
        if (newDisplayLayout.densityDpi() == oldDisplayLayout.densityDpi()) {
            return
        }
        oldDpiLayoutByDisplayId.put(displayId, oldDisplayLayout)
        val oldStableBounds = Rect()
        val newStableBounds = Rect()
        oldDisplayLayout.getStableBounds(oldStableBounds)
        newDisplayLayout.getStableBounds(newStableBounds)
        when {
            oldStableBounds == newStableBounds -> {}
            // Width update means resolution is updated, and we should wait for TRANSIT_CHANGE
            // transition to apply new resolution logic.
            displayResolutionChanged(oldDisplayLayout, newDisplayLayout) -> {
                transitions.registerObserver(this)
                boundsChangedByDisplayId.add(displayId)
            }
            taskbarInsetsUpdated(oldStableBounds, newStableBounds) -> {
                stableBoundsChangedByDisplayId.add(displayId)
            }
        }
        resizeTasksIfPreconditionsSatisfied(displayId, newConfig)
    }

    override fun onTransitionReady(
        transition: IBinder,
        info: TransitionInfo,
        startTransaction: SurfaceControl.Transaction,
        finishTransaction: SurfaceControl.Transaction,
    ) {
        val displayId = info.changes[0].endDisplayId
        val config = displayConfigById[displayId]
        if (info.type == TRANSIT_CHANGE) {
            resizeTasksIfPreconditionsSatisfied(displayId, config, true)
        }
    }

    override fun onStableInsetsChanging(displayId: Int, oldLayout: DisplayLayout?) {
        val oldStableBounds = Rect()
        val newStableBounds = Rect()
        val oldestLayout = oldDpiLayoutByDisplayId[displayId] ?: oldLayout
        val newLayout = displayController.getDisplayLayout(displayId)
        val config = displayConfigById[displayId]
        if (oldestLayout == null || newLayout == null) return
        oldDpiLayoutByDisplayId.put(displayId, oldestLayout)
        oldestLayout.getStableBounds(oldStableBounds)
        newLayout.getStableBounds(newStableBounds)
        when {
            oldStableBounds == newStableBounds -> {
                // No change in stable bounds.
            }
            // Width or height updates mean the resolution has changed.
            displayResolutionChanged(oldestLayout, newLayout) -> {
                boundsChangedByDisplayId.add(displayId)
            }

            taskbarInsetsUpdated(oldStableBounds, newStableBounds) -> {
                stableBoundsChangedByDisplayId.add(displayId)
            }
        }
        resizeTasksIfPreconditionsSatisfied(displayId, config)
    }

    private fun displayResolutionChanged(
        oldestLayout: DisplayLayout,
        newLayout: DisplayLayout,
    ): Boolean =
        oldestLayout.width() != newLayout.width() || oldestLayout.height() != newLayout.height()

    private fun taskbarInsetsUpdated(oldStableBounds: Rect, newStableBounds: Rect): Boolean =
        oldStableBounds.bottom != newStableBounds.bottom

    private fun resizeTasksIfPreconditionsSatisfied(
        displayId: Int,
        config: Configuration?,
        boundsChangeReady: Boolean = false,
    ) {
        when {
            config == null -> {}
            dpiChangedAndInsetsReadyForDisplay(displayId) -> {
                desktopTasksController.onDisplayDpiChanging(
                    displayId,
                    config,
                    oldDpiLayoutByDisplayId[displayId],
                )
                oldDpiLayoutByDisplayId.remove(displayId)
                stableBoundsChangedByDisplayId.remove(displayId)
            }
            resolutionChangedAndInsetsReadyForDisplay(displayId, boundsChangeReady) -> {
                desktopTasksController.onDisplayDpiChanging(
                    displayId,
                    config,
                    oldDpiLayoutByDisplayId[displayId],
                )
                transitions.unregisterObserver(this)
                oldDpiLayoutByDisplayId.remove(displayId)
                boundsChangedByDisplayId.remove(displayId)
            }
        }
    }

    private fun dpiChangedAndInsetsReadyForDisplay(displayId: Int): Boolean =
        displayId in oldDpiLayoutByDisplayId && displayId in stableBoundsChangedByDisplayId

    private fun resolutionChangedAndInsetsReadyForDisplay(
        displayId: Int,
        transitionReady: Boolean,
    ): Boolean =
        displayId in oldDpiLayoutByDisplayId &&
            displayId in boundsChangedByDisplayId &&
            transitionReady

    override fun onDisplayAdded(displayId: Int) =
        traceSection(
            Trace.TRACE_TAG_WINDOW_MANAGER,
+156 −14
Original line number Diff line number Diff line
@@ -137,6 +137,7 @@ import com.android.wm.shell.desktopmode.data.DesktopRepository.DeskChangeListene
import com.android.wm.shell.desktopmode.data.DesktopRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.data.DesktopRepositoryInitializer
import com.android.wm.shell.desktopmode.data.DesktopRepositoryInitializer.DeskRecreationFactory
import com.android.wm.shell.desktopmode.data.persistence.DesktopTaskTilingState
import com.android.wm.shell.desktopmode.desktopfirst.DesktopFirstListenerManager
import com.android.wm.shell.desktopmode.desktopfirst.isDisplayDesktopFirst
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
@@ -187,6 +188,7 @@ import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import com.android.wm.shell.windowdecor.extension.isFullscreen
import com.android.wm.shell.windowdecor.extension.isMultiWindow
import com.android.wm.shell.windowdecor.extension.requestingImmersive
import com.android.wm.shell.windowdecor.tiling.DesktopTilingWindowDecoration.Companion.getDividerBoundsForZombieSession
import com.android.wm.shell.windowdecor.tiling.SnapEventHandler
import com.android.wm.shell.windowdecor.tiling.TilingDisplayReconnectEventHandler
import java.io.PrintWriter
@@ -942,7 +944,55 @@ class DesktopTasksController(
                val taskIds = desktopRepository.getActiveTaskIdsInDesk(deskId)
                for (taskId in taskIds) {
                    val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: continue
                    applyFreeformDisplayChange(wct, task, destinationDisplayId, deskId)
                    val taskTilingState =
                        when (taskId) {
                            desktopRepository.getLeftTiledTask(deskId) ->
                                DesktopTaskTilingState.LEFT
                            desktopRepository.getRightTiledTask(deskId) ->
                                DesktopTaskTilingState.RIGHT
                            else -> DesktopTaskTilingState.NONE
                        }
                    val newStableBounds = Rect()
                    val oldStableBounds = Rect()
                    val sourceLayout = displayController.getDisplayLayout(task.displayId) ?: return
                    val destLayout = destDisplayLayout ?: return
                    destLayout.getStableBounds(newStableBounds)
                    sourceLayout.getStableBounds(oldStableBounds)
                    val newDisplayContext =
                        displayController.getDisplayContext(destinationDisplayId) ?: return
                    val newToOldDpiRatio =
                        destLayout.densityDpi().toDouble() / sourceLayout.densityDpi().toDouble()
                    val dividerBounds: Rect? =
                        when (taskTilingState) {
                            DesktopTaskTilingState.LEFT ->
                                getDividerBoundsForZombieSession(
                                    task.configuration.windowConfiguration.bounds,
                                    null,
                                    newStableBounds,
                                    oldStableBounds,
                                    newToOldDpiRatio,
                                    newDisplayContext,
                                )
                            DesktopTaskTilingState.RIGHT ->
                                getDividerBoundsForZombieSession(
                                    null,
                                    task.configuration.windowConfiguration.bounds,
                                    newStableBounds,
                                    oldStableBounds,
                                    newToOldDpiRatio,
                                    newDisplayContext,
                                )
                            else -> null
                        }
                    applyFreeformDisplayChange(
                        wct,
                        task,
                        destDisplayLayout,
                        displayController.getDisplayLayout(task.displayId),
                        taskTilingState,
                        deskId,
                        dividerBounds,
                    )
                }
                runOnTransitStartList.add { transition ->
                    desksTransitionObserver.addPendingTransition(
@@ -968,6 +1018,58 @@ class DesktopTasksController(
        }
    }

    fun onDisplayDpiChanging(
        displayId: Int,
        newConfig: Configuration,
        oldDisplayLayout: DisplayLayout?,
    ) {
        if (!DesktopExperienceFlags.ENABLE_DISPLAY_DISCONNECT_INTERACTION.isTrue) return
        val newDisplayLayout = displayController.getDisplayLayout(displayId) ?: return
        if (oldDisplayLayout == null) return
        val oldStableBounds = Rect()
        oldDisplayLayout.getStableBounds(oldStableBounds)
        val newToOldDpiRatio =
            newDisplayLayout.densityDpi().toDouble() / oldDisplayLayout.densityDpi()
        snapEventHandler.onDisplayLayoutChange(
            displayId,
            newConfig,
            oldStableBounds,
            newToOldDpiRatio,
        )
        val stableBounds = Rect()
        newDisplayLayout?.getStableBounds(stableBounds)

        val wct = WindowContainerTransaction()
        val userId = userRepositories.current.userId
        userRepositories.forAllRepositories { userRepo ->
            if (userId == userRepo.userId) {
                val deskIds = userRepo.getDeskIds(displayId).toList()
                for (deskId in deskIds) {
                    val deskTasks = userRepo.getActiveTaskIdsInDesk(deskId)
                    if (deskTasks.isEmpty()) continue
                    for (taskId in deskTasks) {
                        val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: continue
                        val taskTilingState =
                            when (taskId) {
                                userRepo.getLeftTiledTask(deskId) -> DesktopTaskTilingState.LEFT
                                userRepo.getRightTiledTask(deskId) -> DesktopTaskTilingState.RIGHT
                                else -> DesktopTaskTilingState.NONE
                            }
                        applyFreeformDisplayChange(
                            wct,
                            task,
                            newDisplayLayout,
                            oldDisplayLayout,
                            taskTilingState,
                            deskId,
                        )
                    }
                }
            }
        }
        transitions.startTransition(TRANSIT_CHANGE, wct, null)
    }

    private fun handleProjectedModeDisconnect(
        desktopRepository: DesktopRepository,
        wct: WindowContainerTransaction,
@@ -2539,7 +2641,16 @@ class DesktopTasksController(
                        wct.setAppBounds(task.token, appBounds)
                    }
                } else if (DesktopExperienceFlags.ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT.isTrue) {
                    applyFreeformDisplayChange(wct, task, displayId, destinationDeskId)
                    applyFreeformDisplayChange(
                        wct,
                        task,
                        displayController.getDisplayLayout(displayId),
                        displayController.getDisplayLayout(task.displayId),
                        // Tiling state will be broken if it exists when a task is moved to next
                        // display.
                        DesktopTaskTilingState.NONE,
                        destinationDeskId,
                    )
                }
            }

@@ -4576,20 +4687,24 @@ class DesktopTasksController(
    }

    /**
     * Apply changes to move a freeform task from one display to another, which includes handling
     * density changes between displays.
     * Apply changes to move a freeform task on display or it's setting changing, which includes
     * handling density changes between displays or on the same display.
     */
    private fun applyFreeformDisplayChange(
        wct: WindowContainerTransaction,
        taskInfo: RunningTaskInfo,
        destDisplayId: Int,
        destDeskId: Int,
        destLayout: DisplayLayout?,
        sourceLayout: DisplayLayout?,
        taskTilingState: DesktopTaskTilingState,
        deskId: Int,
        updatedDividerBounds: Rect? = null,
    ) {
        val sourceLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
        val destLayout = displayController.getDisplayLayout(destDisplayId) ?: return
        if (sourceLayout == null || destLayout == null) return
        val bounds = taskInfo.configuration.windowConfiguration.bounds
        val scaledWidth = bounds.width() * destLayout.densityDpi() / sourceLayout.densityDpi()
        val scaledHeight = bounds.height() * destLayout.densityDpi() / sourceLayout.densityDpi()
        val newToOldDpiRatio =
            destLayout.densityDpi().toDouble() / sourceLayout.densityDpi().toDouble()
        val scaledWidth = bounds.width() * newToOldDpiRatio
        val scaledHeight = bounds.height() * newToOldDpiRatio
        val sourceWidthMargin = sourceLayout.width() - bounds.width()
        val sourceHeightMargin = sourceLayout.height() - bounds.height()
        val destWidthMargin = destLayout.width() - scaledWidth
@@ -4606,12 +4721,39 @@ class DesktopTasksController(
            } else {
                destHeightMargin / 2
            }
        val sourceStableBounds = Rect()
        val destStableBounds = Rect()
        sourceLayout.getStableBounds(sourceStableBounds)
        destLayout.getStableBounds(destStableBounds)
        val boundsWithinDisplay =
            if (destWidthMargin >= 0 && destHeightMargin >= 0) {
                Rect(0, 0, scaledWidth, scaledHeight).apply {
            if (taskTilingState == DesktopTaskTilingState.LEFT) {
                val dividerBounds =
                    updatedDividerBounds ?: snapEventHandler.getDividerBounds(deskId)
                Rect(
                    destStableBounds.left,
                    destStableBounds.top,
                    dividerBounds.left,
                    destStableBounds.bottom,
                )
            } else if (taskTilingState == DesktopTaskTilingState.RIGHT) {
                val dividerBounds =
                    updatedDividerBounds ?: snapEventHandler.getDividerBounds(deskId)
                Rect(
                    dividerBounds.right,
                    destStableBounds.top,
                    destStableBounds.right,
                    destStableBounds.bottom,
                )
            } else if (
                destWidthMargin >= 0 &&
                    destHeightMargin >= 0 &&
                    (bounds.width() < sourceStableBounds.width() ||
                        bounds.height() < sourceStableBounds.height())
            ) {
                Rect(0, 0, scaledWidth.toInt(), scaledHeight.toInt()).apply {
                    offsetTo(
                        scaledLeft.coerceIn(0, destWidthMargin),
                        scaledTop.coerceIn(0, destHeightMargin),
                        scaledLeft.coerceIn(0.0, destWidthMargin).toInt(),
                        scaledTop.coerceIn(0.0, destHeightMargin).toInt(),
                    )
                }
            } else {
+12 −0
Original line number Diff line number Diff line
@@ -1058,6 +1058,18 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
        mDesktopTilingDecorViewModel.onExplodedViewReorder(deskId, topTaskId);
    }

    @Override
    public void onDisplayLayoutChange(int displayId, Configuration config,
            @NonNull Rect oldStableBounds, double newToOldDpiRatio) {
        mDesktopTilingDecorViewModel.onDisplayLayoutChange(displayId, config, oldStableBounds,
                newToOldDpiRatio);
    }

    @Override
    public @NonNull Rect getDividerBounds(int deskId) {
        return mDesktopTilingDecorViewModel.getDividerBounds(deskId);
    }

    @Override
    public void onDeskActivated(int deskId, int displayId) {
        if (mDesktopTilingDecorViewModel.tilingDeskActive(deskId)) {
Loading