Implement move to desktop and fullscreen

Add methods to move a task to fullscreen of freeform in proto2
controller.
Use these methods in window captions to move a task to desktop and back
to fullscreen.

Bug: 261234251
Test: atest DesktopTasksControllerTest
Change-Id: I9907038ad3882240b1f70b6f0c29c677cb4f4a51
This commit is contained in:
Ats Jenk
2022-12-06 11:25:58 -08:00
parent 99156bd05d
commit e29f11f831
6 changed files with 236 additions and 43 deletions

View File

@@ -190,7 +190,8 @@ public abstract class WMShellModule {
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
SyncTransactionQueue syncQueue,
Optional<DesktopModeController> desktopModeController) {
Optional<DesktopModeController> desktopModeController,
Optional<DesktopTasksController> desktopTasksController) {
return new CaptionWindowDecorViewModel(
context,
mainHandler,
@@ -198,7 +199,8 @@ public abstract class WMShellModule {
taskOrganizer,
displayController,
syncQueue,
desktopModeController);
desktopModeController,
desktopTasksController);
}
//

View File

@@ -16,7 +16,11 @@
package com.android.wm.shell.desktopmode
import android.app.ActivityManager
import android.app.WindowConfiguration
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
import android.view.WindowManager
import android.window.WindowContainerTransaction
@@ -84,6 +88,59 @@ class DesktopTasksController(
}
}
/** Move a task with given `taskId` to desktop */
fun moveToDesktop(taskId: Int) {
shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToDesktop(task) }
}
/** Move a task to desktop */
fun moveToDesktop(task: ActivityManager.RunningTaskInfo) {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDesktop: %d", task.taskId)
val wct = WindowContainerTransaction()
// Bring other apps to front first
bringDesktopAppsToFront(wct)
wct.setWindowingMode(task.getToken(), WindowConfiguration.WINDOWING_MODE_FREEFORM)
wct.reorder(task.getToken(), true /* onTop */)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.startTransition(WindowManager.TRANSIT_CHANGE, wct, null /* handler */)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
}
/** Move a task with given `taskId` to fullscreen */
fun moveToFullscreen(taskId: Int) {
shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToFullscreen(task) }
}
/** Move a task to fullscreen */
fun moveToFullscreen(task: ActivityManager.RunningTaskInfo) {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToFullscreen: %d", task.taskId)
val wct = WindowContainerTransaction()
wct.setWindowingMode(task.getToken(), WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
wct.setBounds(task.getToken(), null)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.startTransition(WindowManager.TRANSIT_CHANGE, wct, null /* handler */)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
}
/**
* Get windowing move for a given `taskId`
*
* @return [WindowingMode] for the task or [WINDOWING_MODE_UNDEFINED] if task is not found
*/
@WindowingMode
fun getTaskWindowingMode(taskId: Int): Int {
return shellTaskOrganizer.getRunningTaskInfo(taskId)?.windowingMode
?: WINDOWING_MODE_UNDEFINED
}
private fun bringDesktopAppsToFront(wct: WindowContainerTransaction) {
val activeTasks = desktopModeTaskRepository.getActiveTasks()

View File

@@ -52,6 +52,7 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.transition.Transitions;
@@ -76,6 +77,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private final SyncTransactionQueue mSyncQueue;
private FreeformTaskTransitionStarter mTransitionStarter;
private Optional<DesktopModeController> mDesktopModeController;
private Optional<DesktopTasksController> mDesktopTasksController;
private boolean mTransitionDragActive;
private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -91,7 +93,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
SyncTransactionQueue syncQueue,
Optional<DesktopModeController> desktopModeController) {
Optional<DesktopModeController> desktopModeController,
Optional<DesktopTasksController> desktopTasksController) {
this(
context,
mainHandler,
@@ -100,6 +103,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
displayController,
syncQueue,
desktopModeController,
desktopTasksController,
new CaptionWindowDecoration.Factory(),
InputManager::getInstance);
}
@@ -112,6 +116,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
DisplayController displayController,
SyncTransactionQueue syncQueue,
Optional<DesktopModeController> desktopModeController,
Optional<DesktopTasksController> desktopTasksController,
CaptionWindowDecoration.Factory captionWindowDecorFactory,
Supplier<InputManager> inputManagerSupplier) {
@@ -123,6 +128,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
mDisplayController = displayController;
mSyncQueue = syncQueue;
mDesktopModeController = desktopModeController;
mDesktopTasksController = desktopTasksController;
mCaptionWindowDecorFactory = captionWindowDecorFactory;
mInputManagerSupplier = inputManagerSupplier;
@@ -248,11 +254,13 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
decoration.createHandleMenu();
} else if (id == R.id.desktop_button) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
mDesktopTasksController.ifPresent(c -> c.moveToDesktop(mTaskId));
decoration.closeHandleMenu();
} else if (id == R.id.fullscreen_button) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
decoration.closeHandleMenu();
decoration.setButtonVisibility();
decoration.setButtonVisibility(false);
}
}
@@ -305,8 +313,13 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
*/
private void handleEventForMove(MotionEvent e) {
RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
if (mDesktopModeController.isPresent()
&& mDesktopModeController.get().getDisplayAreaWindowingMode(taskInfo.displayId)
if (DesktopModeStatus.isProto2Enabled()
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
return;
}
if (DesktopModeStatus.isProto1Enabled() && mDesktopModeController.isPresent()
&& mDesktopModeController.get().getDisplayAreaWindowingMode(
taskInfo.displayId)
== WINDOWING_MODE_FULLSCREEN) {
return;
}
@@ -330,9 +343,20 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
.stableInsets().top;
mDragResizeCallback.onDragResizeEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
if (e.getRawY(dragPointerIdx) <= statusBarHeight
&& DesktopModeStatus.isActive(mContext)) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
if (e.getRawY(dragPointerIdx) <= statusBarHeight) {
if (DesktopModeStatus.isProto2Enabled()) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
// Switch a single task to fullscreen
mDesktopTasksController.ifPresent(
c -> c.moveToFullscreen(taskInfo));
}
} else if (DesktopModeStatus.isProto1Enabled()) {
if (DesktopModeStatus.isActive(mContext)) {
// Turn off desktop mode
mDesktopModeController.ifPresent(
c -> c.setDesktopModeActive(false));
}
}
}
break;
}
@@ -420,13 +444,27 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
* @param ev the {@link MotionEvent} received by {@link EventReceiver}
*/
private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
if (!DesktopModeStatus.isActive(mContext)) {
handleCaptionThroughStatusBar(ev);
if (DesktopModeStatus.isProto2Enabled()) {
CaptionWindowDecoration focusedDecor = getFocusedDecor();
if (focusedDecor == null
|| focusedDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
handleCaptionThroughStatusBar(ev);
}
} else if (DesktopModeStatus.isProto1Enabled()) {
if (!DesktopModeStatus.isActive(mContext)) {
handleCaptionThroughStatusBar(ev);
}
}
handleEventOutsideFocusedCaption(ev);
// Prevent status bar from reacting to a caption drag.
if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) {
inputMonitor.pilferPointers();
if (DesktopModeStatus.isProto2Enabled()) {
if (mTransitionDragActive) {
inputMonitor.pilferPointers();
}
} else if (DesktopModeStatus.isProto1Enabled()) {
if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) {
inputMonitor.pilferPointers();
}
}
}
@@ -455,9 +493,20 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
case MotionEvent.ACTION_DOWN: {
// Begin drag through status bar if applicable.
CaptionWindowDecoration focusedDecor = getFocusedDecor();
if (focusedDecor != null && !DesktopModeStatus.isActive(mContext)
&& focusedDecor.checkTouchEventInHandle(ev)) {
mTransitionDragActive = true;
if (focusedDecor != null) {
boolean dragFromStatusBarAllowed = false;
if (DesktopModeStatus.isProto2Enabled()) {
// In proto2 any full screen task can be dragged to freeform
dragFromStatusBarAllowed = focusedDecor.mTaskInfo.getWindowingMode()
== WINDOWING_MODE_FULLSCREEN;
} else if (DesktopModeStatus.isProto1Enabled()) {
// In proto1 task can be dragged to freeform when not in desktop mode
dragFromStatusBarAllowed = !DesktopModeStatus.isActive(mContext);
}
if (dragFromStatusBarAllowed && focusedDecor.checkTouchEventInHandle(ev)) {
mTransitionDragActive = true;
}
}
break;
}
@@ -472,7 +521,13 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
int statusBarHeight = mDisplayController
.getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top;
if (ev.getY() > statusBarHeight) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
if (DesktopModeStatus.isProto2Enabled()) {
mDesktopTasksController.ifPresent(
c -> c.moveToDesktop(focusedDecor.mTaskInfo));
} else if (DesktopModeStatus.isProto1Enabled()) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
}
return;
}
}

View File

@@ -16,8 +16,9 @@
package com.android.wm.shell.windowdecor;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -117,7 +118,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
? R.dimen.freeform_decor_shadow_focused_thickness
: R.dimen.freeform_decor_shadow_unfocused_thickness;
final boolean isFreeform =
taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM;
taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
WindowDecorLinearLayout oldRootView = mResult.mRootView;
@@ -167,11 +168,17 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
// If this task is not focused, do not show caption.
setCaptionVisibility(mTaskInfo.isFocused);
// Only handle should show if Desktop Mode is inactive.
boolean desktopCurrentStatus = DesktopModeStatus.isActive(mContext);
if (mDesktopActive != desktopCurrentStatus && mTaskInfo.isFocused) {
mDesktopActive = desktopCurrentStatus;
setButtonVisibility();
if (mTaskInfo.isFocused) {
if (DesktopModeStatus.isProto2Enabled()) {
updateButtonVisibility();
} else if (DesktopModeStatus.isProto1Enabled()) {
// Only handle should show if Desktop Mode is inactive.
boolean desktopCurrentStatus = DesktopModeStatus.isActive(mContext);
if (mDesktopActive != desktopCurrentStatus) {
mDesktopActive = desktopCurrentStatus;
setButtonVisibility(mDesktopActive);
}
}
}
if (!isDragResizeable) {
@@ -214,7 +221,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
View handle = caption.findViewById(R.id.caption_handle);
handle.setOnTouchListener(mOnCaptionTouchListener);
handle.setOnClickListener(mOnCaptionButtonClickListener);
setButtonVisibility();
updateButtonVisibility();
}
private void setupHandleMenu() {
@@ -244,14 +251,25 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
/**
* Sets the visibility of buttons and color of caption based on desktop mode status
*/
void setButtonVisibility() {
mDesktopActive = DesktopModeStatus.isActive(mContext);
int v = mDesktopActive ? View.VISIBLE : View.GONE;
void updateButtonVisibility() {
if (DesktopModeStatus.isProto2Enabled()) {
setButtonVisibility(mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM);
} else if (DesktopModeStatus.isProto1Enabled()) {
mDesktopActive = DesktopModeStatus.isActive(mContext);
setButtonVisibility(mDesktopActive);
}
}
/**
* Show or hide buttons
*/
void setButtonVisibility(boolean visible) {
int visibility = visible ? View.VISIBLE : View.GONE;
View caption = mResult.mRootView.findViewById(R.id.caption);
View back = caption.findViewById(R.id.back_button);
View close = caption.findViewById(R.id.close_window);
back.setVisibility(v);
close.setVisibility(v);
back.setVisibility(visibility);
close.setVisibility(visibility);
int buttonTintColorRes =
mDesktopActive ? R.color.decor_button_dark_color
: R.color.decor_button_light_color;
@@ -260,7 +278,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
View handle = caption.findViewById(R.id.caption_handle);
VectorDrawable handleBackground = (VectorDrawable) handle.getBackground();
handleBackground.setTintList(buttonTintColor);
caption.getBackground().setTint(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT);
caption.getBackground().setTint(visible ? Color.WHITE : Color.TRANSPARENT);
}
boolean isHandleMenuActive() {

View File

@@ -17,6 +17,9 @@
package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.testing.AndroidTestingRunner
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
@@ -29,6 +32,7 @@ import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
@@ -118,8 +122,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
fun showDesktopApps_allAppsInvisible_bringsToFront() {
val homeTask = setUpHomeTask()
val task1 = setUpDesktopTask()
val task2 = setUpDesktopTask()
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskHidden(task1)
markTaskHidden(task2)
@@ -136,25 +140,21 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
fun showDesktopApps_appsAlreadyVisible_doesNothing() {
setUpHomeTask()
val task1 = setUpDesktopTask()
val task2 = setUpDesktopTask()
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskVisible(task1)
markTaskVisible(task2)
controller.showDesktopApps()
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
verify(transitions, never()).startTransition(anyInt(), any(), isNull())
} else {
verify(shellTaskOrganizer, never()).applyTransaction(any())
}
verifyWCTNotExecuted()
}
@Test
fun showDesktopApps_someAppsInvisible_reordersAll() {
val homeTask = setUpHomeTask()
val task1 = setUpDesktopTask()
val task2 = setUpDesktopTask()
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskHidden(task1)
markTaskVisible(task2)
@@ -179,7 +179,49 @@ class DesktopTasksControllerTest : ShellTestCase() {
wct.assertReorderAt(index = 0, homeTask)
}
private fun setUpDesktopTask(): RunningTaskInfo {
@Test
fun moveToDesktop() {
val task = setUpFullscreenTask()
controller.moveToDesktop(task)
val wct = getLatestWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
}
@Test
fun moveToDesktop_nonExistentTask_doesNothing() {
controller.moveToDesktop(999)
verifyWCTNotExecuted()
}
@Test
fun moveToFullscreen() {
val task = setUpFreeformTask()
controller.moveToFullscreen(task)
val wct = getLatestWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FULLSCREEN)
}
@Test
fun moveToFullscreen_nonExistentTask_doesNothing() {
controller.moveToFullscreen(999)
verifyWCTNotExecuted()
}
@Test
fun getTaskWindowingMode() {
val fullscreenTask = setUpFullscreenTask()
val freeformTask = setUpFreeformTask()
assertThat(controller.getTaskWindowingMode(fullscreenTask.taskId))
.isEqualTo(WINDOWING_MODE_FULLSCREEN)
assertThat(controller.getTaskWindowingMode(freeformTask.taskId))
.isEqualTo(WINDOWING_MODE_FREEFORM)
assertThat(controller.getTaskWindowingMode(999)).isEqualTo(WINDOWING_MODE_UNDEFINED)
}
private fun setUpFreeformTask(): RunningTaskInfo {
val task = createFreeformTask()
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
desktopModeTaskRepository.addActiveTask(task.taskId)
@@ -195,6 +237,13 @@ class DesktopTasksControllerTest : ShellTestCase() {
return task
}
private fun setUpFullscreenTask(): RunningTaskInfo {
val task = createFullscreenTask()
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
runningTasks.add(task)
return task
}
private fun markTaskVisible(task: RunningTaskInfo) {
desktopModeTaskRepository.updateVisibleFreeformTasks(task.taskId, visible = true)
}
@@ -212,6 +261,14 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
return arg.value
}
private fun verifyWCTNotExecuted() {
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
verify(transitions, never()).startTransition(anyInt(), any(), isNull())
} else {
verify(shellTaskOrganizer, never()).applyTransaction(any())
}
}
}
private fun WindowContainerTransaction.assertReorderAt(index: Int, task: RunningTaskInfo) {

View File

@@ -47,6 +47,7 @@ import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import org.junit.Before;
import org.junit.Test;
@@ -76,6 +77,8 @@ public class CaptionWindowDecorViewModelTests extends ShellTestCase {
@Mock private DesktopModeController mDesktopModeController;
@Mock private DesktopTasksController mDesktopTasksController;
@Mock private InputMonitor mInputMonitor;
@Mock private InputChannel mInputChannel;
@@ -103,6 +106,7 @@ public class CaptionWindowDecorViewModelTests extends ShellTestCase {
mDisplayController,
mSyncQueue,
Optional.of(mDesktopModeController),
Optional.of(mDesktopTasksController),
mCaptionWindowDecorFactory,
new MockObjectSupplier<>(mMockInputManagers, () -> mock(InputManager.class)));
mCaptionWindowDecorViewModel.setEventReceiverFactory(mEventReceiverFactory);