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

Commit f656c995 authored by Matt Sziklay's avatar Matt Sziklay
Browse files

Reconnect: Prevent multiple restoreDisplay calls for the same display.

Store the uniqueDisplayId of any displays while they are being restored. As long as that display restoration is in progress, no-op any other requests.

Bug: 432731538
Bug: 432321729
Bug: 427832518
Bug: 432321729
Bug: 427832518
Test: Manual; reconnect display, observe in logcat restore operations only happen once
Flag: com.android.window.flags.enable_display_reconnect_interaction
Change-Id: Ibc39040cb067bfac6f865c80428be6322644d1b5
parent 2e35ee42
Loading
Loading
Loading
Loading
+39 −11
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.content.Context
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.window.DesktopExperienceFlags
@@ -27,6 +28,7 @@ import android.window.DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_ACTIVATION
import android.window.DesktopModeFlags
import android.window.DisplayAreaInfo
import com.android.app.tracing.traceSection
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener
@@ -77,6 +79,10 @@ class DesktopDisplayEventHandler(
    // displayId to its uniqueId since we will not be able to fetch it after disconnect.
    private val uniqueIdByDisplayId = mutableMapOf<Int, String>()

    // All uniqueDisplayIds that are currently being restored; any further requests
    // to restore them will no-op.
    @VisibleForTesting val displaysMidRestoration = ArraySet<String>()

    init {
        shellInit.addInitCallback({ onInit() }, this)
    }
@@ -141,6 +147,14 @@ class DesktopDisplayEventHandler(
            if (displayId != DEFAULT_DISPLAY) {
                desktopDisplayModeController.updateDefaultDisplayWindowingMode()
            }
            val uniqueDisplayId = uniqueIdByDisplayId[displayId]
            if (uniqueDisplayId != null && uniqueDisplayId in displaysMidRestoration) {
                logW(
                    "onDisplayRemoved: Found display mid-restoration that did not finish: " +
                        "displayId=$displayId, uniqueDisplayId=$uniqueDisplayId"
                )
                displaysMidRestoration.remove(uniqueDisplayId)
            }
            uniqueIdByDisplayId.remove(displayId)
        }

@@ -189,20 +203,28 @@ class DesktopDisplayEventHandler(
        val uniqueDisplayId = displayController.getDisplay(displayId)?.uniqueId ?: return false
        uniqueIdByDisplayId[displayId] = uniqueDisplayId
        val currentUserRepository = desktopUserRepositories.current
        if (
            !DesktopExperienceFlags.ENABLE_DISPLAY_RECONNECT_INTERACTION.isTrue ||
                !currentUserRepository.hasPreservedDisplayForUniqueDisplayId(uniqueDisplayId)
        ) {
        if (!DesktopExperienceFlags.ENABLE_DISPLAY_RECONNECT_INTERACTION.isTrue) {
            logV("handlePotentialReconnect: Reconnect not supported; aborting.")
            return false
        }
        val preservedTasks = currentUserRepository.getPreservedTasks(uniqueDisplayId)
        if (uniqueDisplayId in displaysMidRestoration) {
            logV("handlePotentialReconnect: uniqueDisplay=$uniqueDisplayId " +
                "mid-restoration; aborting.")
            return false
        }
        if (!currentUserRepository.hasPreservedDisplayForUniqueDisplayId(uniqueDisplayId)) {
            logV("handlePotentialReconnect: No preserved display found for " +
                "uniqueDisplayId=$uniqueDisplayId; aborting.")
        }
        val preservedTasks =
            currentUserRepository.getPreservedTasks(uniqueDisplayId).toMutableList()
        // Projected mode: Do not move anything focused on the internal display.
        if (!desktopState.isDesktopModeSupportedOnDisplay(DEFAULT_DISPLAY)) {
            val focusedDefaultDisplayTaskIds =
                desktopTasksController
                    .getFocusedNonDesktopTasks(DEFAULT_DISPLAY, currentUserRepository.userId)
                    .map { task -> task.taskId }
            preservedTasks.filterNot { taskId -> focusedDefaultDisplayTaskIds.contains(taskId) }
            preservedTasks.removeAll { taskId -> focusedDefaultDisplayTaskIds.contains(taskId) }
        }
        if (preservedTasks.isEmpty()) {
            // The preserved display is normally removed at the end of restoreDisplay.
@@ -210,11 +232,17 @@ class DesktopDisplayEventHandler(
            currentUserRepository.removePreservedDisplay(uniqueDisplayId)
            return false
        }
        displaysMidRestoration.add(uniqueDisplayId)
        mainScope.launch {
            // If the display has been removed by the time this executes, restore nothing.
            if (uniqueDisplayId !in displaysMidRestoration) return@launch
            desktopTasksController.restoreDisplay(
                displayId = displayId,
                uniqueDisplayId = uniqueDisplayId,
                userId = desktopUserRepositories.current.userId,
            )
            displaysMidRestoration.remove(uniqueDisplayId)
        }
        return true
    }

+18 −0
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import com.android.wm.shell.shared.desktopmode.FakeDesktopState
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.sysui.UserChangeListener
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
@@ -456,9 +457,26 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
            .thenReturn(emptyList())

        onDisplaysChangedListenerCaptor.lastValue.onDesktopModeEligibleChanged(externalDisplayId)
        testScope.runCurrent()

        verify(mockDesktopTasksController)
            .restoreDisplay(eq(externalDisplayId), eq(UNIQUE_DISPLAY_ID), eq(PRIMARY_USER_ID))
        assertThat(handler.displaysMidRestoration).isEmpty()
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_RECONNECT_INTERACTION)
    fun testPotentialReconnect_noOpWhenPendingRestorePresent() {
        handler.displaysMidRestoration.add(UNIQUE_DISPLAY_ID)
        desktopState.overrideDesktopModeSupportPerDisplay[externalDisplayId] = true
        whenever(mockDesktopRepository.hasPreservedDisplayForUniqueDisplayId(UNIQUE_DISPLAY_ID))
            .thenReturn(true)

        onDisplaysChangedListenerCaptor.lastValue.onDesktopModeEligibleChanged(externalDisplayId)
        testScope.runCurrent()

        verify(mockDesktopTasksController, never())
            .restoreDisplay(externalDisplayId, UNIQUE_DISPLAY_ID, PRIMARY_USER_ID)
    }

    @Test