Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +4 −2 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -198,7 +199,8 @@ public abstract class WMShellModule { taskOrganizer, displayController, syncQueue, desktopModeController); desktopModeController, desktopTasksController); } // Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +57 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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() Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +70 −15 Original line number Diff line number Diff line Loading @@ -53,6 +53,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; Loading @@ -75,6 +76,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<>(); Loading @@ -90,7 +92,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { ShellTaskOrganizer taskOrganizer, DisplayController displayController, SyncTransactionQueue syncQueue, Optional<DesktopModeController> desktopModeController) { Optional<DesktopModeController> desktopModeController, Optional<DesktopTasksController> desktopTasksController) { this( context, mainHandler, Loading @@ -99,6 +102,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { displayController, syncQueue, desktopModeController, desktopTasksController, new CaptionWindowDecoration.Factory(), new InputMonitorFactory()); } Loading @@ -112,6 +116,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { DisplayController displayController, SyncTransactionQueue syncQueue, Optional<DesktopModeController> desktopModeController, Optional<DesktopTasksController> desktopTasksController, CaptionWindowDecoration.Factory captionWindowDecorFactory, InputMonitorFactory inputMonitorFactory) { mContext = context; Loading @@ -122,6 +127,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mDisplayController = displayController; mSyncQueue = syncQueue; mDesktopModeController = desktopModeController; mDesktopTasksController = desktopTasksController; mCaptionWindowDecorFactory = captionWindowDecorFactory; mInputMonitorFactory = inputMonitorFactory; Loading Loading @@ -242,11 +248,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); } } Loading Loading @@ -299,8 +307,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; } Loading @@ -324,9 +337,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; } Loading Loading @@ -408,15 +432,29 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { * @param ev the {@link MotionEvent} received by {@link EventReceiver} */ private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) { 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 (DesktopModeStatus.isProto2Enabled()) { if (mTransitionDragActive) { inputMonitor.pilferPointers(); } } else if (DesktopModeStatus.isProto1Enabled()) { if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) { inputMonitor.pilferPointers(); } } } // If an UP/CANCEL action is received outside of caption bounds, turn off handle menu private void handleEventOutsideFocusedCaption(MotionEvent ev) { Loading @@ -443,10 +481,21 @@ 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)) { 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; } case MotionEvent.ACTION_UP: { Loading @@ -460,7 +509,13 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { int statusBarHeight = mDisplayController .getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top; if (ev.getY() > statusBarHeight) { if (DesktopModeStatus.isProto2Enabled()) { mDesktopTasksController.ifPresent( c -> c.moveToDesktop(focusedDecor.mTaskInfo)); } else if (DesktopModeStatus.isProto1Enabled()) { mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true)); } return; } } Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +32 −14 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -167,11 +168,17 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL // If this task is not focused, do not show caption. setCaptionVisibility(mTaskInfo.isFocused); 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 && mTaskInfo.isFocused) { if (mDesktopActive != desktopCurrentStatus) { mDesktopActive = desktopCurrentStatus; setButtonVisibility(); setButtonVisibility(mDesktopActive); } } } if (!isDragResizeable) { Loading Loading @@ -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() { Loading Loading @@ -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() { void updateButtonVisibility() { if (DesktopModeStatus.isProto2Enabled()) { setButtonVisibility(mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM); } else if (DesktopModeStatus.isProto1Enabled()) { mDesktopActive = DesktopModeStatus.isActive(mContext); int v = mDesktopActive ? View.VISIBLE : View.GONE; 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; Loading @@ -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() { Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +69 −12 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading @@ -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) } Loading @@ -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) { Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +4 −2 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -198,7 +199,8 @@ public abstract class WMShellModule { taskOrganizer, displayController, syncQueue, desktopModeController); desktopModeController, desktopTasksController); } // Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +57 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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() Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +70 −15 Original line number Diff line number Diff line Loading @@ -53,6 +53,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; Loading @@ -75,6 +76,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<>(); Loading @@ -90,7 +92,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { ShellTaskOrganizer taskOrganizer, DisplayController displayController, SyncTransactionQueue syncQueue, Optional<DesktopModeController> desktopModeController) { Optional<DesktopModeController> desktopModeController, Optional<DesktopTasksController> desktopTasksController) { this( context, mainHandler, Loading @@ -99,6 +102,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { displayController, syncQueue, desktopModeController, desktopTasksController, new CaptionWindowDecoration.Factory(), new InputMonitorFactory()); } Loading @@ -112,6 +116,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { DisplayController displayController, SyncTransactionQueue syncQueue, Optional<DesktopModeController> desktopModeController, Optional<DesktopTasksController> desktopTasksController, CaptionWindowDecoration.Factory captionWindowDecorFactory, InputMonitorFactory inputMonitorFactory) { mContext = context; Loading @@ -122,6 +127,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mDisplayController = displayController; mSyncQueue = syncQueue; mDesktopModeController = desktopModeController; mDesktopTasksController = desktopTasksController; mCaptionWindowDecorFactory = captionWindowDecorFactory; mInputMonitorFactory = inputMonitorFactory; Loading Loading @@ -242,11 +248,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); } } Loading Loading @@ -299,8 +307,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; } Loading @@ -324,9 +337,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; } Loading Loading @@ -408,15 +432,29 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { * @param ev the {@link MotionEvent} received by {@link EventReceiver} */ private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) { 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 (DesktopModeStatus.isProto2Enabled()) { if (mTransitionDragActive) { inputMonitor.pilferPointers(); } } else if (DesktopModeStatus.isProto1Enabled()) { if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) { inputMonitor.pilferPointers(); } } } // If an UP/CANCEL action is received outside of caption bounds, turn off handle menu private void handleEventOutsideFocusedCaption(MotionEvent ev) { Loading @@ -443,10 +481,21 @@ 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)) { 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; } case MotionEvent.ACTION_UP: { Loading @@ -460,7 +509,13 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { int statusBarHeight = mDisplayController .getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top; if (ev.getY() > statusBarHeight) { if (DesktopModeStatus.isProto2Enabled()) { mDesktopTasksController.ifPresent( c -> c.moveToDesktop(focusedDecor.mTaskInfo)); } else if (DesktopModeStatus.isProto1Enabled()) { mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true)); } return; } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +32 −14 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -167,11 +168,17 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL // If this task is not focused, do not show caption. setCaptionVisibility(mTaskInfo.isFocused); 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 && mTaskInfo.isFocused) { if (mDesktopActive != desktopCurrentStatus) { mDesktopActive = desktopCurrentStatus; setButtonVisibility(); setButtonVisibility(mDesktopActive); } } } if (!isDragResizeable) { Loading Loading @@ -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() { Loading Loading @@ -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() { void updateButtonVisibility() { if (DesktopModeStatus.isProto2Enabled()) { setButtonVisibility(mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM); } else if (DesktopModeStatus.isProto1Enabled()) { mDesktopActive = DesktopModeStatus.isActive(mContext); int v = mDesktopActive ? View.VISIBLE : View.GONE; 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; Loading @@ -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() { Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +69 −12 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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) Loading @@ -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) Loading @@ -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) Loading @@ -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) } Loading @@ -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) { Loading