Merge "Add an API to desktop mode to stash desktop apps" into udc-qpr-dev
This commit is contained in:
@@ -508,5 +508,15 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
|
||||
);
|
||||
return result[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stashDesktopApps(int displayId) throws RemoteException {
|
||||
// Stashing of desktop apps not needed. Apps always launch on desktop
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTaskListener(IDesktopTaskListener listener) throws RemoteException {
|
||||
// TODO(b/261234402): move visibility from sysui state to listener
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ class DesktopModeTaskRepository {
|
||||
*/
|
||||
val activeTasks: ArraySet<Int> = ArraySet(),
|
||||
val visibleTasks: ArraySet<Int> = ArraySet(),
|
||||
var stashed: Boolean = false
|
||||
)
|
||||
|
||||
// Tasks currently in freeform mode, ordered from top to bottom (top is at index 0).
|
||||
@@ -311,6 +312,33 @@ class DesktopModeTaskRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update stashed status on display with id [displayId]
|
||||
*/
|
||||
fun setStashed(displayId: Int, stashed: Boolean) {
|
||||
val data = displayData.getOrCreate(displayId)
|
||||
val oldValue = data.stashed
|
||||
data.stashed = stashed
|
||||
if (oldValue != stashed) {
|
||||
KtProtoLog.d(
|
||||
WM_SHELL_DESKTOP_MODE,
|
||||
"DesktopTaskRepo: mark stashed=%b displayId=%d",
|
||||
stashed,
|
||||
displayId
|
||||
)
|
||||
visibleTasksListeners.forEach { (listener, executor) ->
|
||||
executor.execute { listener.onStashedChanged(displayId, stashed) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if display with id [displayId] has desktop tasks stashed
|
||||
*/
|
||||
fun isStashed(displayId: Int): Boolean {
|
||||
return displayData[displayId]?.stashed ?: false
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines interface for classes that can listen to changes for active tasks in desktop mode.
|
||||
*/
|
||||
@@ -331,5 +359,11 @@ class DesktopModeTaskRepository {
|
||||
*/
|
||||
@JvmDefault
|
||||
fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {}
|
||||
|
||||
/**
|
||||
* Called when the desktop stashed status changes.
|
||||
*/
|
||||
@JvmDefault
|
||||
fun onStashedChanged(displayId: Int, stashed: Boolean) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ import com.android.wm.shell.common.ExecutorUtils
|
||||
import com.android.wm.shell.common.ExternalInterfaceBinder
|
||||
import com.android.wm.shell.common.RemoteCallable
|
||||
import com.android.wm.shell.common.ShellExecutor
|
||||
import com.android.wm.shell.common.SingleInstanceRemoteListener
|
||||
import com.android.wm.shell.common.SyncTransactionQueue
|
||||
import com.android.wm.shell.common.annotations.ExternalThread
|
||||
import com.android.wm.shell.common.annotations.ShellMainThread
|
||||
@@ -118,6 +119,18 @@ class DesktopTasksController(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stash desktop tasks on display with id [displayId].
|
||||
*
|
||||
* When desktop tasks are stashed, launcher home screen icons are fully visible. New apps
|
||||
* launched in this state will be added to the desktop. Existing desktop tasks will be brought
|
||||
* back to front during the launch.
|
||||
*/
|
||||
fun stashDesktopApps(displayId: Int) {
|
||||
KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: stashDesktopApps")
|
||||
desktopModeTaskRepository.setStashed(displayId, true)
|
||||
}
|
||||
|
||||
/** Get number of tasks that are marked as visible */
|
||||
fun getVisibleTaskCount(displayId: Int): Int {
|
||||
return desktopModeTaskRepository.getVisibleTaskCount(displayId)
|
||||
@@ -397,6 +410,11 @@ class DesktopTasksController(
|
||||
transition: IBinder,
|
||||
request: TransitionRequestInfo
|
||||
): WindowContainerTransaction? {
|
||||
KtProtoLog.v(
|
||||
WM_SHELL_DESKTOP_MODE,
|
||||
"DesktopTasksController: handleRequest request=%s",
|
||||
request
|
||||
)
|
||||
// Check if we should skip handling this transition
|
||||
val shouldHandleRequest =
|
||||
when {
|
||||
@@ -418,43 +436,63 @@ class DesktopTasksController(
|
||||
}
|
||||
|
||||
val task: RunningTaskInfo = request.triggerTask
|
||||
val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId)
|
||||
|
||||
// Check if we should switch a fullscreen task to freeform
|
||||
if (task.windowingMode == WINDOWING_MODE_FULLSCREEN) {
|
||||
// If there are any visible desktop tasks, switch the task to freeform
|
||||
if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
|
||||
KtProtoLog.d(
|
||||
WM_SHELL_DESKTOP_MODE,
|
||||
"DesktopTasksController: switch fullscreen task to freeform on transition" +
|
||||
" taskId=%d",
|
||||
task.taskId
|
||||
)
|
||||
return WindowContainerTransaction().also { wct ->
|
||||
addMoveToDesktopChanges(wct, task.token)
|
||||
}
|
||||
}
|
||||
return when {
|
||||
// If display has tasks stashed, handle as stashed launch
|
||||
desktopModeTaskRepository.isStashed(task.displayId) -> handleStashedTaskLaunch(task)
|
||||
// Check if fullscreen task should be updated
|
||||
task.windowingMode == WINDOWING_MODE_FULLSCREEN -> handleFullscreenTaskLaunch(task)
|
||||
// Check if freeform task should be updated
|
||||
task.windowingMode == WINDOWING_MODE_FREEFORM -> handleFreeformTaskLaunch(task)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
// CHeck if we should switch a freeform task to fullscreen
|
||||
if (task.windowingMode == WINDOWING_MODE_FREEFORM) {
|
||||
// If no visible desktop tasks, switch this task to freeform as the transition came
|
||||
// outside of this controller
|
||||
if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) {
|
||||
KtProtoLog.d(
|
||||
private fun handleFreeformTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
|
||||
val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId)
|
||||
if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) {
|
||||
KtProtoLog.d(
|
||||
WM_SHELL_DESKTOP_MODE,
|
||||
"DesktopTasksController: switch freeform task to fullscreen oon transition" +
|
||||
" taskId=%d",
|
||||
" taskId=%d",
|
||||
task.taskId
|
||||
)
|
||||
return WindowContainerTransaction().also { wct ->
|
||||
addMoveToFullscreenChanges(wct, task.token)
|
||||
}
|
||||
)
|
||||
return WindowContainerTransaction().also { wct ->
|
||||
addMoveToFullscreenChanges(wct, task.token)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun handleFullscreenTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
|
||||
val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId)
|
||||
if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
|
||||
KtProtoLog.d(
|
||||
WM_SHELL_DESKTOP_MODE,
|
||||
"DesktopTasksController: switch fullscreen task to freeform on transition" +
|
||||
" taskId=%d",
|
||||
task.taskId
|
||||
)
|
||||
return WindowContainerTransaction().also { wct ->
|
||||
addMoveToDesktopChanges(wct, task.token)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun handleStashedTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction {
|
||||
KtProtoLog.d(
|
||||
WM_SHELL_DESKTOP_MODE,
|
||||
"DesktopTasksController: launch apps with stashed on transition taskId=%d",
|
||||
task.taskId
|
||||
)
|
||||
val wct = WindowContainerTransaction()
|
||||
bringDesktopAppsToFront(task.displayId, wct)
|
||||
addMoveToDesktopChanges(wct, task.token)
|
||||
desktopModeTaskRepository.setStashed(task.displayId, false)
|
||||
return wct
|
||||
}
|
||||
|
||||
private fun addMoveToDesktopChanges(
|
||||
wct: WindowContainerTransaction,
|
||||
token: WindowContainerToken
|
||||
@@ -658,8 +696,46 @@ class DesktopTasksController(
|
||||
@BinderThread
|
||||
private class IDesktopModeImpl(private var controller: DesktopTasksController?) :
|
||||
IDesktopMode.Stub(), ExternalInterfaceBinder {
|
||||
|
||||
private lateinit var remoteListener:
|
||||
SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>
|
||||
|
||||
private val listener: VisibleTasksListener = object : VisibleTasksListener {
|
||||
override fun onVisibilityChanged(displayId: Int, visible: Boolean) {
|
||||
// TODO(b/261234402): move visibility from sysui state to listener
|
||||
remoteListener.call { l -> l.onVisibilityChanged(displayId, visible) }
|
||||
}
|
||||
|
||||
override fun onStashedChanged(displayId: Int, stashed: Boolean) {
|
||||
KtProtoLog.v(
|
||||
WM_SHELL_DESKTOP_MODE,
|
||||
"IDesktopModeImpl: onStashedChanged stashed=%b display=%d",
|
||||
stashed,
|
||||
displayId
|
||||
)
|
||||
remoteListener.call { l -> l.onStashedChanged(displayId, stashed) }
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
remoteListener =
|
||||
SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>(
|
||||
controller,
|
||||
{ c ->
|
||||
c.desktopModeTaskRepository.addVisibleTasksListener(
|
||||
listener,
|
||||
c.mainExecutor
|
||||
)
|
||||
},
|
||||
{ c ->
|
||||
c.desktopModeTaskRepository.removeVisibleTasksListener(listener)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/** Invalidates this instance, preventing future calls from updating the controller. */
|
||||
override fun invalidate() {
|
||||
remoteListener.unregister()
|
||||
controller = null
|
||||
}
|
||||
|
||||
@@ -670,6 +746,13 @@ class DesktopTasksController(
|
||||
) { c -> c.showDesktopApps(displayId) }
|
||||
}
|
||||
|
||||
override fun stashDesktopApps(displayId: Int) {
|
||||
ExecutorUtils.executeRemoteCallWithTaskPermission(
|
||||
controller,
|
||||
"stashDesktopApps"
|
||||
) { c -> c.stashDesktopApps(displayId) }
|
||||
}
|
||||
|
||||
override fun getVisibleTaskCount(displayId: Int): Int {
|
||||
val result = IntArray(1)
|
||||
ExecutorUtils.executeRemoteCallWithTaskPermission(
|
||||
@@ -680,6 +763,18 @@ class DesktopTasksController(
|
||||
)
|
||||
return result[0]
|
||||
}
|
||||
|
||||
override fun setTaskListener(listener: IDesktopTaskListener?) {
|
||||
KtProtoLog.v(
|
||||
WM_SHELL_DESKTOP_MODE,
|
||||
"IDesktopModeImpl: set task listener=%s",
|
||||
listener ?: "null"
|
||||
)
|
||||
ExecutorUtils.executeRemoteCallWithTaskPermission(
|
||||
controller,
|
||||
"setTaskListener"
|
||||
) { _ -> listener?.let { remoteListener.register(it) } ?: remoteListener.unregister() }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.wm.shell.desktopmode;
|
||||
|
||||
import com.android.wm.shell.desktopmode.IDesktopTaskListener;
|
||||
|
||||
/**
|
||||
* Interface that is exposed to remote callers to manipulate desktop mode features.
|
||||
*/
|
||||
@@ -24,6 +26,12 @@ interface IDesktopMode {
|
||||
/** Show apps on the desktop on the given display */
|
||||
void showDesktopApps(int displayId);
|
||||
|
||||
/** Stash apps on the desktop to allow launching another app from home screen */
|
||||
void stashDesktopApps(int displayId);
|
||||
|
||||
/** Get count of visible desktop tasks on the given display */
|
||||
int getVisibleTaskCount(int displayId);
|
||||
|
||||
/** Set listener that will receive callbacks about updates to desktop tasks */
|
||||
oneway void setTaskListener(IDesktopTaskListener listener);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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;
|
||||
|
||||
/**
|
||||
* Allows external processes to register a listener in WMShell to get updates about desktop task
|
||||
* state.
|
||||
*/
|
||||
interface IDesktopTaskListener {
|
||||
|
||||
/** Desktop task visibility has change. Visible if at least 1 task is visible. */
|
||||
oneway void onVisibilityChanged(int displayId, boolean visible);
|
||||
|
||||
/** Desktop task stashed status has changed. */
|
||||
oneway void onStashedChanged(int displayId, boolean stashed);
|
||||
}
|
||||
@@ -313,6 +313,65 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
|
||||
assertThat(tasks.first()).isEqualTo(6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setStashed_stateIsUpdatedForTheDisplay() {
|
||||
repo.setStashed(DEFAULT_DISPLAY, true)
|
||||
assertThat(repo.isStashed(DEFAULT_DISPLAY)).isTrue()
|
||||
assertThat(repo.isStashed(SECOND_DISPLAY)).isFalse()
|
||||
|
||||
repo.setStashed(DEFAULT_DISPLAY, false)
|
||||
assertThat(repo.isStashed(DEFAULT_DISPLAY)).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setStashed_notifyListener() {
|
||||
val listener = TestVisibilityListener()
|
||||
val executor = TestShellExecutor()
|
||||
repo.addVisibleTasksListener(listener, executor)
|
||||
repo.setStashed(DEFAULT_DISPLAY, true)
|
||||
executor.flushAll()
|
||||
assertThat(listener.stashedOnDefaultDisplay).isTrue()
|
||||
assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1)
|
||||
|
||||
repo.setStashed(DEFAULT_DISPLAY, false)
|
||||
executor.flushAll()
|
||||
assertThat(listener.stashedOnDefaultDisplay).isFalse()
|
||||
assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setStashed_secondCallDoesNotNotify() {
|
||||
val listener = TestVisibilityListener()
|
||||
val executor = TestShellExecutor()
|
||||
repo.addVisibleTasksListener(listener, executor)
|
||||
repo.setStashed(DEFAULT_DISPLAY, true)
|
||||
repo.setStashed(DEFAULT_DISPLAY, true)
|
||||
executor.flushAll()
|
||||
assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setStashed_tracksPerDisplay() {
|
||||
val listener = TestVisibilityListener()
|
||||
val executor = TestShellExecutor()
|
||||
repo.addVisibleTasksListener(listener, executor)
|
||||
|
||||
repo.setStashed(DEFAULT_DISPLAY, true)
|
||||
executor.flushAll()
|
||||
assertThat(listener.stashedOnDefaultDisplay).isTrue()
|
||||
assertThat(listener.stashedOnSecondaryDisplay).isFalse()
|
||||
|
||||
repo.setStashed(SECOND_DISPLAY, true)
|
||||
executor.flushAll()
|
||||
assertThat(listener.stashedOnDefaultDisplay).isTrue()
|
||||
assertThat(listener.stashedOnSecondaryDisplay).isTrue()
|
||||
|
||||
repo.setStashed(DEFAULT_DISPLAY, false)
|
||||
executor.flushAll()
|
||||
assertThat(listener.stashedOnDefaultDisplay).isFalse()
|
||||
assertThat(listener.stashedOnSecondaryDisplay).isTrue()
|
||||
}
|
||||
|
||||
class TestListener : DesktopModeTaskRepository.ActiveTasksListener {
|
||||
var activeChangesOnDefaultDisplay = 0
|
||||
var activeChangesOnSecondaryDisplay = 0
|
||||
@@ -332,6 +391,12 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
|
||||
var visibleChangesOnDefaultDisplay = 0
|
||||
var visibleChangesOnSecondaryDisplay = 0
|
||||
|
||||
var stashedOnDefaultDisplay = false
|
||||
var stashedOnSecondaryDisplay = false
|
||||
|
||||
var stashedChangesOnDefaultDisplay = 0
|
||||
var stashedChangesOnSecondaryDisplay = 0
|
||||
|
||||
override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {
|
||||
when (displayId) {
|
||||
DEFAULT_DISPLAY -> {
|
||||
@@ -345,6 +410,20 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
|
||||
else -> fail("Visible task listener received unexpected display id: $displayId")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStashedChanged(displayId: Int, stashed: Boolean) {
|
||||
when (displayId) {
|
||||
DEFAULT_DISPLAY -> {
|
||||
stashedOnDefaultDisplay = stashed
|
||||
stashedChangesOnDefaultDisplay++
|
||||
}
|
||||
SECOND_DISPLAY -> {
|
||||
stashedOnSecondaryDisplay = stashed
|
||||
stashedChangesOnDefaultDisplay++
|
||||
}
|
||||
else -> fail("Visible task listener received unexpected display id: $displayId")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -450,6 +450,27 @@ class DesktopTasksControllerTest : ShellTestCase() {
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleRequest_fullscreenTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
|
||||
assumeTrue(ENABLE_SHELL_TRANSITIONS)
|
||||
|
||||
val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY)
|
||||
markTaskHidden(stashedFreeformTask)
|
||||
|
||||
val fullscreenTask = createFullscreenTask(DEFAULT_DISPLAY)
|
||||
|
||||
controller.stashDesktopApps(DEFAULT_DISPLAY)
|
||||
|
||||
val result = controller.handleRequest(Binder(), createTransition(fullscreenTask))
|
||||
assertThat(result).isNotNull()
|
||||
result!!.assertReorderSequence(stashedFreeformTask, fullscreenTask)
|
||||
assertThat(result.changes[fullscreenTask.token.asBinder()]?.windowingMode)
|
||||
.isEqualTo(WINDOWING_MODE_FREEFORM)
|
||||
|
||||
// Stashed state should be cleared
|
||||
assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleRequest_freeformTask_freeformVisible_returnNull() {
|
||||
assumeTrue(ENABLE_SHELL_TRANSITIONS)
|
||||
@@ -500,6 +521,25 @@ class DesktopTasksControllerTest : ShellTestCase() {
|
||||
.isEqualTo(WINDOWING_MODE_FULLSCREEN)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleRequest_freeformTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
|
||||
assumeTrue(ENABLE_SHELL_TRANSITIONS)
|
||||
|
||||
val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY)
|
||||
markTaskHidden(stashedFreeformTask)
|
||||
|
||||
val freeformTask = createFreeformTask(DEFAULT_DISPLAY)
|
||||
|
||||
controller.stashDesktopApps(DEFAULT_DISPLAY)
|
||||
|
||||
val result = controller.handleRequest(Binder(), createTransition(freeformTask))
|
||||
assertThat(result).isNotNull()
|
||||
result?.assertReorderSequence(stashedFreeformTask, freeformTask)
|
||||
|
||||
// Stashed state should be cleared
|
||||
assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleRequest_notOpenOrToFrontTransition_returnNull() {
|
||||
assumeTrue(ENABLE_SHELL_TRANSITIONS)
|
||||
@@ -539,6 +579,14 @@ class DesktopTasksControllerTest : ShellTestCase() {
|
||||
assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stashDesktopApps_stateUpdates() {
|
||||
controller.stashDesktopApps(DEFAULT_DISPLAY)
|
||||
|
||||
assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isTrue()
|
||||
assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isFalse()
|
||||
}
|
||||
|
||||
private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
|
||||
val task = createFreeformTask(displayId)
|
||||
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
|
||||
|
||||
Reference in New Issue
Block a user