Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/data/DesktopDisplay.kt 0 → 100644 +24 −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.data /** A display that supports desktops. */ class DesktopDisplay(val displayId: Int) { // The set implementation must preserve order. val orderedDesks: MutableSet<Desk> = mutableSetOf() var activeDeskId: Int? = null } libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/data/DesktopRepository.kt +1 −7 Original line number Diff line number Diff line Loading @@ -54,13 +54,6 @@ class DesktopRepository( val userId: Int, val desktopConfig: DesktopConfig, ) { /** A display that supports desktops. */ private class DesktopDisplay(val displayId: Int) { // The set implementation must preserve order. val orderedDesks: MutableSet<Desk> = mutableSetOf() var activeDeskId: Int? = null } /** Tiling data preserved after a display got disconnected. */ data class PreservedTiledAppData(val leftTiledTask: Int?, val rightTiledTask: Int?) Loading Loading @@ -1267,6 +1260,7 @@ class DesktopRepository( userId, desks, getActiveDeskId(displayId), preservedDisplaysByUniqueId, ) } catch (exception: Exception) { logE( Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/data/DesktopRepositoryInitializerImpl.kt +186 −148 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.wm.shell.desktopmode.data import android.content.Context import android.graphics.Rect import android.view.Display.DEFAULT_DISPLAY import android.view.Display.INVALID_DISPLAY import android.window.DesktopExperienceFlags Loading @@ -30,6 +31,7 @@ import com.android.wm.shell.desktopmode.data.persistence.DesktopPersistentReposi import com.android.wm.shell.desktopmode.data.persistence.DesktopRepositoryState import com.android.wm.shell.desktopmode.data.persistence.DesktopTaskState import com.android.wm.shell.desktopmode.data.persistence.DesktopTaskTilingState import com.android.wm.shell.desktopmode.data.persistence.Rect as RectProto import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.shared.desktopmode.DesktopConfig Loading Loading @@ -81,28 +83,64 @@ class DesktopRepositoryInitializerImpl( val desktopRepositoryState = persistentRepository.getDesktopRepositoryState(userId) ?: continue val desksToRestore = getDesksToRestore(desktopRepositoryState, userId) val preservedDesksToRestore = getPreservedDesksToRestore(desktopRepositoryState) logV( "initialize() will restore desks=%s user=%d", "initialize() will restore desks=%s preservedDesks=%s user=%d", desksToRestore.map { it.desktopId }, preservedDesksToRestore.map { it.desktopId }, userId, ) for (persistentDesktop in desksToRestore) { restoreDesktop( desktopRepositoryState, persistentDesktop, uniqueIdToDisplayIdMap, desktopSupportedOnDefaultDisplay, userId, repository, wasPreservedDisplay = false, ) } for (preservedDesktop in preservedDesksToRestore) { restoreDesktop( desktopRepositoryState, preservedDesktop, uniqueIdToDisplayIdMap, desktopSupportedOnDefaultDisplay, userId, repository, wasPreservedDisplay = true, ) } } } finally { _isInitialized.value = true } } } /** TODO: b/444034767 - Consider splitting this method into pieces. */ private suspend fun restoreDesktop( desktopRepositoryState: DesktopRepositoryState, persistentDesktop: Desktop, uniqueIdToDisplayIdMap: MutableMap<String, Int>?, desktopSupportedOnDefaultDisplay: Boolean, userId: Int, repository: DesktopRepository, wasPreservedDisplay: Boolean, ) { val maxTasks = getTaskLimit(persistentDesktop) var uniqueDisplayId = persistentDesktop.uniqueDisplayId // TODO: b/441767264 - Consider waiting for DisplayController to receive all // displayAdded signals before initializing here. This way we don't // rely on an IPC if the display is not yet available. val displayIdIfNotFound = if ( DesktopExperienceFlags.ENABLE_EXTERNAL_DISPLAY_PERSISTENCE_BUGFIX .isTrue ) { if (DesktopExperienceFlags.ENABLE_EXTERNAL_DISPLAY_PERSISTENCE_BUGFIX.isTrue) { displayController.getDisplayIdByUniqueIdBlocking(uniqueDisplayId) } else { DEFAULT_DISPLAY } var newDisplayId = uniqueIdToDisplayIdMap?.get(uniqueDisplayId) ?: displayIdIfNotFound var newDisplayId = uniqueIdToDisplayIdMap?.get(uniqueDisplayId) ?: displayIdIfNotFound val deskId = persistentDesktop.desktopId var transientDesk = false var preserveDesk = false Loading @@ -110,7 +148,7 @@ class DesktopRepositoryInitializerImpl( // If a desk is somehow going to the default display on an // unsupported device, skip it. logV("desk=%d is going to the default display, skipping", deskId) continue return } if (newDisplayId == INVALID_DISPLAY) { val result = Loading @@ -119,6 +157,7 @@ class DesktopRepositoryInitializerImpl( uniqueDisplayId, uniqueIdToDisplayIdMap, desktopSupportedOnDefaultDisplay, wasPreservedDisplay, ) newDisplayId = result.newDisplayId uniqueDisplayId = result.newUniqueDisplayId Loading Loading @@ -148,13 +187,12 @@ class DesktopRepositoryInitializerImpl( } if (newDeskId == null) { logW( "Could not re-create desk=%d from uniqueDisplayId=%d " + "in displayId=%d", "Could not re-create desk=%d from uniqueDisplayId=%d " + "in displayId=%d", deskId, uniqueDisplayId, newDisplayId, ) continue return } // TODO: b/393961770 - [DesktopRepository] doesn't save desks to the Loading Loading @@ -184,7 +222,7 @@ class DesktopRepositoryInitializerImpl( deskId = newDeskId, taskId = task.taskId, isVisible = false, taskBounds = null, taskBounds = task.taskBounds.toRect(), ) if (isVisible) { Loading @@ -197,22 +235,15 @@ class DesktopRepositoryInitializerImpl( ) } val tilingEnabled = DesktopExperienceFlags.ENABLE_TILE_RESIZING.isTrue() if ( tilingEnabled && task.desktopTaskTilingState == DesktopTaskTilingState.LEFT ) { val tilingEnabled = DesktopExperienceFlags.ENABLE_TILE_RESIZING.isTrue() if (tilingEnabled && task.desktopTaskTilingState == DesktopTaskTilingState.LEFT) { repository.addLeftTiledTaskToDesk( persistentDesktop.displayId, task.taskId, newDeskId, ) } if ( tilingEnabled && task.desktopTaskTilingState == DesktopTaskTilingState.RIGHT ) { if (tilingEnabled && task.desktopTaskTilingState == DesktopTaskTilingState.RIGHT) { repository.addRightTiledTaskToDesk( persistentDesktop.displayId, task.taskId, Loading @@ -232,12 +263,6 @@ class DesktopRepositoryInitializerImpl( repository.preserveDesk(newDeskId, persistentDesktop.uniqueDisplayId) } } } } finally { _isInitialized.value = true } } } private suspend fun getDesksToRestore( state: DesktopRepositoryState, Loading @@ -256,13 +281,23 @@ class DesktopRepositoryInitializerImpl( (!limitToSingleDeskPerDisplay || isValidSingleDesk) } } .toSet() .toMutableSet() } private suspend fun getPreservedDesksToRestore(state: DesktopRepositoryState): Set<Desktop> { // Also get all preserved desks. If we end up not finding their displays, we'll // preserve them again. This allows us to restore a display during boot. val desktopSet = mutableSetOf<Desktop>() state.preservedDisplayByUniqueIdMap.values.forEach { preservedDisplay -> desktopSet.addAll(preservedDisplay.getPreservedDesktop().values) } return desktopSet } /** * Handles the case where the initial display ID is invalid. Redirects the desk to * DEFAULT_DISPLAY, marking it to be preserved and marking as transient if desktops are not * supported on the default display. * supported on the default display or if it was originally a preserved desk. * * @param deskId The ID of the desk being processed. * @param currentUniqueDisplayId The current unique display ID. Loading @@ -274,17 +309,18 @@ class DesktopRepositoryInitializerImpl( currentUniqueDisplayId: String?, uniqueIdToDisplayIdMap: Map<String, Int>?, desktopSupportedOnDefaultDisplay: Boolean, wasPreservedDisplay: Boolean, ): DisplayRedirectResult { logV( "desk=%d: displayId for uniqueDisplayId=%s not found, redirecting to default display", "desk=%d: displayId for uniqueDisplayId=%s not found; handling", deskId, currentUniqueDisplayId, ) val newDisplayId = DEFAULT_DISPLAY val newUniqueDisplayId = uniqueIdToDisplayIdMap?.entries?.firstOrNull { it.value == DEFAULT_DISPLAY }?.key val transientDesk = !desktopSupportedOnDefaultDisplay // If a desk is being redirected to the default display but desks val transientDesk = !desktopSupportedOnDefaultDisplay || wasPreservedDisplay // If a desk was preserved or is being redirected to the default display when desks // aren't supported there, mark it as transient; we will still create // it to mimic the desk as faithfully as possible, but it will // only be used for preservation for a future restore. It will be Loading @@ -301,6 +337,8 @@ class DesktopRepositoryInitializerImpl( ) } private fun RectProto.toRect() = Rect(left, top, right, bottom) private fun getTaskLimit(persistedDesk: Desktop): Int = desktopConfig.maxTaskLimit.takeIf { it > 0 } ?: persistedDesk.zOrderedTasksCount Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/data/persistence/DesktopPersistentRepository.kt +65 −8 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ package com.android.wm.shell.desktopmode.data.persistence import android.content.Context import android.graphics.Rect import android.util.ArrayMap import android.util.ArraySet import android.util.Log import android.view.Display.DEFAULT_DISPLAY Loading @@ -29,6 +31,8 @@ import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler import androidx.datastore.dataStoreFile import com.android.framework.protobuf.InvalidProtocolBufferException import com.android.wm.shell.desktopmode.data.Desk import com.android.wm.shell.desktopmode.data.DesktopDisplay import com.android.wm.shell.desktopmode.data.persistence.Rect as RectProto import com.android.wm.shell.shared.annotations.ShellBackgroundThread import java.io.IOException import java.io.InputStream Loading Loading @@ -109,7 +113,12 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis null } suspend fun addOrUpdateRepository(userId: Int, desks: List<Desk>, activeDeskId: Int?) { suspend fun addOrUpdateRepository( userId: Int, desks: List<Desk>, activeDeskId: Int?, preservedDisplays: ArrayMap<String, DesktopDisplay>, ) { try { dataStore.updateData { persistentRepositories: DesktopPersistentRepositories -> val currentRepository = Loading @@ -132,6 +141,7 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis desk.freeformTasksInZOrder, desk.leftTiledTaskId, desk.rightTiledTaskId, desk.boundsByTaskId, ) .updateZOrder(desk.freeformTasksInZOrder) .updateUniqueDisplayId(desk.uniqueDisplayId) Loading @@ -151,14 +161,15 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis } } } if ( DesktopExperienceFlags.ENABLE_EXTERNAL_DISPLAY_PERSISTENCE_BUGFIX.isTrue && activeDeskId == null ) { if (DesktopExperienceFlags.ENABLE_EXTERNAL_DISPLAY_PERSISTENCE_BUGFIX.isTrue) { if (activeDeskId == null) { desks.first().uniqueDisplayId.let { currentUserRepoBuilder.removeActiveDeskByUniqueDisplayId(it) } } addOrUpdatePreservedDisplays(currentUserRepoBuilder, preservedDisplays) } persistentRepositories .toBuilder() .putDesktopRepoByUser(userId, currentUserRepoBuilder.build()) Loading @@ -174,6 +185,41 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis } } private fun addOrUpdatePreservedDisplays( currentUserRepoBuilder: DesktopRepositoryState.Builder, preservedDisplaysByUniqueId: ArrayMap<String, DesktopDisplay>, ) { currentUserRepoBuilder.clearPreservedDisplayByUniqueId() preservedDisplaysByUniqueId.forEach { preservedDisplay -> val updatedPreservedDisplayBuilder = PreservedDisplay.newBuilder() preservedDisplay.value.activeDeskId?.let { updatedPreservedDisplayBuilder.setActiveDeskId(it) } preservedDisplay.value.orderedDesks.forEach { desk -> val updatedDesktop = Desktop.newBuilder() .setDesktopId(desk.deskId) .setDisplayId(desk.displayId) .updateTaskStates( desk.visibleTasks, desk.minimizedTasks, desk.freeformTasksInZOrder, desk.leftTiledTaskId, desk.rightTiledTaskId, desk.boundsByTaskId, ) .updateZOrder(desk.freeformTasksInZOrder) .updateUniqueDisplayId(desk.uniqueDisplayId) .build() updatedPreservedDisplayBuilder.putPreservedDesktop(desk.deskId, updatedDesktop) } currentUserRepoBuilder.putPreservedDisplayByUniqueId( preservedDisplay.key, updatedPreservedDisplayBuilder.build(), ) } } /** Adds or updates a desktop stored in the datastore */ @Deprecated("Use addOrUpdateRepository() instead.", ReplaceWith("addOrUpdateRepository()")) suspend fun addOrUpdateDesktop( Loading Loading @@ -323,6 +369,7 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis freeformTasksInZOrder: ArrayList<Int>, leftTiledTask: Int?, rightTiledTask: Int?, boundsByTaskId: MutableMap<Int, Rect> = mutableMapOf(), ): Desktop.Builder { clearTasksByTaskId() Loading @@ -343,12 +390,17 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis it, state = DesktopTaskState.VISIBLE, getTilingStateForTask(it, leftTiledTask, rightTiledTask), bounds = boundsByTaskId[it] ?: Rect(), ) } ) putAllTasksByTaskId( minimizedTasks.associateWith { createDesktopTask(it, state = DesktopTaskState.MINIMIZED) createDesktopTask( it, state = DesktopTaskState.MINIMIZED, bounds = boundsByTaskId[it] ?: Rect(), ) } ) return this Loading Loading @@ -386,11 +438,16 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis taskId: Int, state: DesktopTaskState = DesktopTaskState.VISIBLE, tilingState: DesktopTaskTilingState = DesktopTaskTilingState.NONE, bounds: Rect, ): DesktopTask = DesktopTask.newBuilder() .setTaskId(taskId) .setDesktopTaskState(state) .setDesktopTaskTilingState(tilingState) .setTaskBounds(bounds.toRectProto()) .build() private fun Rect.toRectProto() = RectProto.newBuilder().setLeft(left).setTop(top).setRight(right).setBottom(bottom) } } libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/data/persistence/persistent_desktop_repositories.proto +18 −0 Original line number Diff line number Diff line Loading @@ -15,10 +15,20 @@ enum DesktopTaskTilingState { RIGHT = 3; } message Rect { optional int32 left = 1; optional int32 top = 2; optional int32 right = 3; optional int32 bottom = 4; } message DesktopTask { optional int32 task_id = 1; optional DesktopTaskState desktop_task_state= 2; optional DesktopTaskTilingState desktop_task_tiling_state = 3; // Tracks task bounds; used primarily for display restore functionality as // core will restore task bounds to the task itself on boot. optional Rect task_bounds = 4; } message Desktop { Loading @@ -30,11 +40,19 @@ message Desktop { optional string unique_display_id = 5; } message PreservedDisplay { // Stores a mapping between a preserved display and its desktops. The key is the desktop id. map<int32, Desktop> preserved_desktop = 1; optional int32 active_desk_id = 2; } message DesktopRepositoryState { // Stores a mapping between a repository and the desktops in it. The key is the desktop id. map<int32, Desktop> desktop = 1; // Stores a mapping of the active desk id associated with each display. map<string, int32> active_desk_by_unique_display_id = 2; // Stores a mapping of preserved displays by their unique display id. map<string, PreservedDisplay> preserved_display_by_unique_id = 3; } message DesktopPersistentRepositories { Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/data/DesktopDisplay.kt 0 → 100644 +24 −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.data /** A display that supports desktops. */ class DesktopDisplay(val displayId: Int) { // The set implementation must preserve order. val orderedDesks: MutableSet<Desk> = mutableSetOf() var activeDeskId: Int? = null }
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/data/DesktopRepository.kt +1 −7 Original line number Diff line number Diff line Loading @@ -54,13 +54,6 @@ class DesktopRepository( val userId: Int, val desktopConfig: DesktopConfig, ) { /** A display that supports desktops. */ private class DesktopDisplay(val displayId: Int) { // The set implementation must preserve order. val orderedDesks: MutableSet<Desk> = mutableSetOf() var activeDeskId: Int? = null } /** Tiling data preserved after a display got disconnected. */ data class PreservedTiledAppData(val leftTiledTask: Int?, val rightTiledTask: Int?) Loading Loading @@ -1267,6 +1260,7 @@ class DesktopRepository( userId, desks, getActiveDeskId(displayId), preservedDisplaysByUniqueId, ) } catch (exception: Exception) { logE( Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/data/DesktopRepositoryInitializerImpl.kt +186 −148 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.wm.shell.desktopmode.data import android.content.Context import android.graphics.Rect import android.view.Display.DEFAULT_DISPLAY import android.view.Display.INVALID_DISPLAY import android.window.DesktopExperienceFlags Loading @@ -30,6 +31,7 @@ import com.android.wm.shell.desktopmode.data.persistence.DesktopPersistentReposi import com.android.wm.shell.desktopmode.data.persistence.DesktopRepositoryState import com.android.wm.shell.desktopmode.data.persistence.DesktopTaskState import com.android.wm.shell.desktopmode.data.persistence.DesktopTaskTilingState import com.android.wm.shell.desktopmode.data.persistence.Rect as RectProto import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.shared.desktopmode.DesktopConfig Loading Loading @@ -81,28 +83,64 @@ class DesktopRepositoryInitializerImpl( val desktopRepositoryState = persistentRepository.getDesktopRepositoryState(userId) ?: continue val desksToRestore = getDesksToRestore(desktopRepositoryState, userId) val preservedDesksToRestore = getPreservedDesksToRestore(desktopRepositoryState) logV( "initialize() will restore desks=%s user=%d", "initialize() will restore desks=%s preservedDesks=%s user=%d", desksToRestore.map { it.desktopId }, preservedDesksToRestore.map { it.desktopId }, userId, ) for (persistentDesktop in desksToRestore) { restoreDesktop( desktopRepositoryState, persistentDesktop, uniqueIdToDisplayIdMap, desktopSupportedOnDefaultDisplay, userId, repository, wasPreservedDisplay = false, ) } for (preservedDesktop in preservedDesksToRestore) { restoreDesktop( desktopRepositoryState, preservedDesktop, uniqueIdToDisplayIdMap, desktopSupportedOnDefaultDisplay, userId, repository, wasPreservedDisplay = true, ) } } } finally { _isInitialized.value = true } } } /** TODO: b/444034767 - Consider splitting this method into pieces. */ private suspend fun restoreDesktop( desktopRepositoryState: DesktopRepositoryState, persistentDesktop: Desktop, uniqueIdToDisplayIdMap: MutableMap<String, Int>?, desktopSupportedOnDefaultDisplay: Boolean, userId: Int, repository: DesktopRepository, wasPreservedDisplay: Boolean, ) { val maxTasks = getTaskLimit(persistentDesktop) var uniqueDisplayId = persistentDesktop.uniqueDisplayId // TODO: b/441767264 - Consider waiting for DisplayController to receive all // displayAdded signals before initializing here. This way we don't // rely on an IPC if the display is not yet available. val displayIdIfNotFound = if ( DesktopExperienceFlags.ENABLE_EXTERNAL_DISPLAY_PERSISTENCE_BUGFIX .isTrue ) { if (DesktopExperienceFlags.ENABLE_EXTERNAL_DISPLAY_PERSISTENCE_BUGFIX.isTrue) { displayController.getDisplayIdByUniqueIdBlocking(uniqueDisplayId) } else { DEFAULT_DISPLAY } var newDisplayId = uniqueIdToDisplayIdMap?.get(uniqueDisplayId) ?: displayIdIfNotFound var newDisplayId = uniqueIdToDisplayIdMap?.get(uniqueDisplayId) ?: displayIdIfNotFound val deskId = persistentDesktop.desktopId var transientDesk = false var preserveDesk = false Loading @@ -110,7 +148,7 @@ class DesktopRepositoryInitializerImpl( // If a desk is somehow going to the default display on an // unsupported device, skip it. logV("desk=%d is going to the default display, skipping", deskId) continue return } if (newDisplayId == INVALID_DISPLAY) { val result = Loading @@ -119,6 +157,7 @@ class DesktopRepositoryInitializerImpl( uniqueDisplayId, uniqueIdToDisplayIdMap, desktopSupportedOnDefaultDisplay, wasPreservedDisplay, ) newDisplayId = result.newDisplayId uniqueDisplayId = result.newUniqueDisplayId Loading Loading @@ -148,13 +187,12 @@ class DesktopRepositoryInitializerImpl( } if (newDeskId == null) { logW( "Could not re-create desk=%d from uniqueDisplayId=%d " + "in displayId=%d", "Could not re-create desk=%d from uniqueDisplayId=%d " + "in displayId=%d", deskId, uniqueDisplayId, newDisplayId, ) continue return } // TODO: b/393961770 - [DesktopRepository] doesn't save desks to the Loading Loading @@ -184,7 +222,7 @@ class DesktopRepositoryInitializerImpl( deskId = newDeskId, taskId = task.taskId, isVisible = false, taskBounds = null, taskBounds = task.taskBounds.toRect(), ) if (isVisible) { Loading @@ -197,22 +235,15 @@ class DesktopRepositoryInitializerImpl( ) } val tilingEnabled = DesktopExperienceFlags.ENABLE_TILE_RESIZING.isTrue() if ( tilingEnabled && task.desktopTaskTilingState == DesktopTaskTilingState.LEFT ) { val tilingEnabled = DesktopExperienceFlags.ENABLE_TILE_RESIZING.isTrue() if (tilingEnabled && task.desktopTaskTilingState == DesktopTaskTilingState.LEFT) { repository.addLeftTiledTaskToDesk( persistentDesktop.displayId, task.taskId, newDeskId, ) } if ( tilingEnabled && task.desktopTaskTilingState == DesktopTaskTilingState.RIGHT ) { if (tilingEnabled && task.desktopTaskTilingState == DesktopTaskTilingState.RIGHT) { repository.addRightTiledTaskToDesk( persistentDesktop.displayId, task.taskId, Loading @@ -232,12 +263,6 @@ class DesktopRepositoryInitializerImpl( repository.preserveDesk(newDeskId, persistentDesktop.uniqueDisplayId) } } } } finally { _isInitialized.value = true } } } private suspend fun getDesksToRestore( state: DesktopRepositoryState, Loading @@ -256,13 +281,23 @@ class DesktopRepositoryInitializerImpl( (!limitToSingleDeskPerDisplay || isValidSingleDesk) } } .toSet() .toMutableSet() } private suspend fun getPreservedDesksToRestore(state: DesktopRepositoryState): Set<Desktop> { // Also get all preserved desks. If we end up not finding their displays, we'll // preserve them again. This allows us to restore a display during boot. val desktopSet = mutableSetOf<Desktop>() state.preservedDisplayByUniqueIdMap.values.forEach { preservedDisplay -> desktopSet.addAll(preservedDisplay.getPreservedDesktop().values) } return desktopSet } /** * Handles the case where the initial display ID is invalid. Redirects the desk to * DEFAULT_DISPLAY, marking it to be preserved and marking as transient if desktops are not * supported on the default display. * supported on the default display or if it was originally a preserved desk. * * @param deskId The ID of the desk being processed. * @param currentUniqueDisplayId The current unique display ID. Loading @@ -274,17 +309,18 @@ class DesktopRepositoryInitializerImpl( currentUniqueDisplayId: String?, uniqueIdToDisplayIdMap: Map<String, Int>?, desktopSupportedOnDefaultDisplay: Boolean, wasPreservedDisplay: Boolean, ): DisplayRedirectResult { logV( "desk=%d: displayId for uniqueDisplayId=%s not found, redirecting to default display", "desk=%d: displayId for uniqueDisplayId=%s not found; handling", deskId, currentUniqueDisplayId, ) val newDisplayId = DEFAULT_DISPLAY val newUniqueDisplayId = uniqueIdToDisplayIdMap?.entries?.firstOrNull { it.value == DEFAULT_DISPLAY }?.key val transientDesk = !desktopSupportedOnDefaultDisplay // If a desk is being redirected to the default display but desks val transientDesk = !desktopSupportedOnDefaultDisplay || wasPreservedDisplay // If a desk was preserved or is being redirected to the default display when desks // aren't supported there, mark it as transient; we will still create // it to mimic the desk as faithfully as possible, but it will // only be used for preservation for a future restore. It will be Loading @@ -301,6 +337,8 @@ class DesktopRepositoryInitializerImpl( ) } private fun RectProto.toRect() = Rect(left, top, right, bottom) private fun getTaskLimit(persistedDesk: Desktop): Int = desktopConfig.maxTaskLimit.takeIf { it > 0 } ?: persistedDesk.zOrderedTasksCount Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/data/persistence/DesktopPersistentRepository.kt +65 −8 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ package com.android.wm.shell.desktopmode.data.persistence import android.content.Context import android.graphics.Rect import android.util.ArrayMap import android.util.ArraySet import android.util.Log import android.view.Display.DEFAULT_DISPLAY Loading @@ -29,6 +31,8 @@ import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler import androidx.datastore.dataStoreFile import com.android.framework.protobuf.InvalidProtocolBufferException import com.android.wm.shell.desktopmode.data.Desk import com.android.wm.shell.desktopmode.data.DesktopDisplay import com.android.wm.shell.desktopmode.data.persistence.Rect as RectProto import com.android.wm.shell.shared.annotations.ShellBackgroundThread import java.io.IOException import java.io.InputStream Loading Loading @@ -109,7 +113,12 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis null } suspend fun addOrUpdateRepository(userId: Int, desks: List<Desk>, activeDeskId: Int?) { suspend fun addOrUpdateRepository( userId: Int, desks: List<Desk>, activeDeskId: Int?, preservedDisplays: ArrayMap<String, DesktopDisplay>, ) { try { dataStore.updateData { persistentRepositories: DesktopPersistentRepositories -> val currentRepository = Loading @@ -132,6 +141,7 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis desk.freeformTasksInZOrder, desk.leftTiledTaskId, desk.rightTiledTaskId, desk.boundsByTaskId, ) .updateZOrder(desk.freeformTasksInZOrder) .updateUniqueDisplayId(desk.uniqueDisplayId) Loading @@ -151,14 +161,15 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis } } } if ( DesktopExperienceFlags.ENABLE_EXTERNAL_DISPLAY_PERSISTENCE_BUGFIX.isTrue && activeDeskId == null ) { if (DesktopExperienceFlags.ENABLE_EXTERNAL_DISPLAY_PERSISTENCE_BUGFIX.isTrue) { if (activeDeskId == null) { desks.first().uniqueDisplayId.let { currentUserRepoBuilder.removeActiveDeskByUniqueDisplayId(it) } } addOrUpdatePreservedDisplays(currentUserRepoBuilder, preservedDisplays) } persistentRepositories .toBuilder() .putDesktopRepoByUser(userId, currentUserRepoBuilder.build()) Loading @@ -174,6 +185,41 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis } } private fun addOrUpdatePreservedDisplays( currentUserRepoBuilder: DesktopRepositoryState.Builder, preservedDisplaysByUniqueId: ArrayMap<String, DesktopDisplay>, ) { currentUserRepoBuilder.clearPreservedDisplayByUniqueId() preservedDisplaysByUniqueId.forEach { preservedDisplay -> val updatedPreservedDisplayBuilder = PreservedDisplay.newBuilder() preservedDisplay.value.activeDeskId?.let { updatedPreservedDisplayBuilder.setActiveDeskId(it) } preservedDisplay.value.orderedDesks.forEach { desk -> val updatedDesktop = Desktop.newBuilder() .setDesktopId(desk.deskId) .setDisplayId(desk.displayId) .updateTaskStates( desk.visibleTasks, desk.minimizedTasks, desk.freeformTasksInZOrder, desk.leftTiledTaskId, desk.rightTiledTaskId, desk.boundsByTaskId, ) .updateZOrder(desk.freeformTasksInZOrder) .updateUniqueDisplayId(desk.uniqueDisplayId) .build() updatedPreservedDisplayBuilder.putPreservedDesktop(desk.deskId, updatedDesktop) } currentUserRepoBuilder.putPreservedDisplayByUniqueId( preservedDisplay.key, updatedPreservedDisplayBuilder.build(), ) } } /** Adds or updates a desktop stored in the datastore */ @Deprecated("Use addOrUpdateRepository() instead.", ReplaceWith("addOrUpdateRepository()")) suspend fun addOrUpdateDesktop( Loading Loading @@ -323,6 +369,7 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis freeformTasksInZOrder: ArrayList<Int>, leftTiledTask: Int?, rightTiledTask: Int?, boundsByTaskId: MutableMap<Int, Rect> = mutableMapOf(), ): Desktop.Builder { clearTasksByTaskId() Loading @@ -343,12 +390,17 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis it, state = DesktopTaskState.VISIBLE, getTilingStateForTask(it, leftTiledTask, rightTiledTask), bounds = boundsByTaskId[it] ?: Rect(), ) } ) putAllTasksByTaskId( minimizedTasks.associateWith { createDesktopTask(it, state = DesktopTaskState.MINIMIZED) createDesktopTask( it, state = DesktopTaskState.MINIMIZED, bounds = boundsByTaskId[it] ?: Rect(), ) } ) return this Loading Loading @@ -386,11 +438,16 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis taskId: Int, state: DesktopTaskState = DesktopTaskState.VISIBLE, tilingState: DesktopTaskTilingState = DesktopTaskTilingState.NONE, bounds: Rect, ): DesktopTask = DesktopTask.newBuilder() .setTaskId(taskId) .setDesktopTaskState(state) .setDesktopTaskTilingState(tilingState) .setTaskBounds(bounds.toRectProto()) .build() private fun Rect.toRectProto() = RectProto.newBuilder().setLeft(left).setTop(top).setRight(right).setBottom(bottom) } }
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/data/persistence/persistent_desktop_repositories.proto +18 −0 Original line number Diff line number Diff line Loading @@ -15,10 +15,20 @@ enum DesktopTaskTilingState { RIGHT = 3; } message Rect { optional int32 left = 1; optional int32 top = 2; optional int32 right = 3; optional int32 bottom = 4; } message DesktopTask { optional int32 task_id = 1; optional DesktopTaskState desktop_task_state= 2; optional DesktopTaskTilingState desktop_task_tiling_state = 3; // Tracks task bounds; used primarily for display restore functionality as // core will restore task bounds to the task itself on boot. optional Rect task_bounds = 4; } message Desktop { Loading @@ -30,11 +40,19 @@ message Desktop { optional string unique_display_id = 5; } message PreservedDisplay { // Stores a mapping between a preserved display and its desktops. The key is the desktop id. map<int32, Desktop> preserved_desktop = 1; optional int32 active_desk_id = 2; } message DesktopRepositoryState { // Stores a mapping between a repository and the desktops in it. The key is the desktop id. map<int32, Desktop> desktop = 1; // Stores a mapping of the active desk id associated with each display. map<string, int32> active_desk_by_unique_display_id = 2; // Stores a mapping of preserved displays by their unique display id. map<string, PreservedDisplay> preserved_display_by_unique_id = 3; } message DesktopPersistentRepositories { Loading