Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +23 −1 Original line number Diff line number Diff line Loading @@ -61,6 +61,7 @@ import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.displayareahelper.DisplayAreaHelperController; import com.android.wm.shell.draganddrop.DragAndDropController; Loading Loading @@ -677,7 +678,11 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static Optional<DesktopMode> provideDesktopMode( Optional<DesktopModeController> desktopModeController) { Optional<DesktopModeController> desktopModeController, Optional<DesktopTasksController> desktopTasksController) { if (DesktopModeStatus.isProto2Enabled()) { return desktopTasksController.map(DesktopTasksController::asDesktopMode); } return desktopModeController.map(DesktopModeController::asDesktopMode); } Loading @@ -698,6 +703,23 @@ public abstract class WMShellBaseModule { return Optional.empty(); } @BindsOptionalOf @DynamicOverride abstract DesktopTasksController optionalDesktopTasksController(); @WMSingleton @Provides static Optional<DesktopTasksController> providesDesktopTasksController( @DynamicOverride Optional<Lazy<DesktopTasksController>> desktopTasksController) { // Use optional-of-lazy for the dependency that this provider relies on. // Lazy ensures that this provider will not be the cause the dependency is created // when it will not be returned due to the condition below. if (DesktopModeStatus.isProto2Enabled()) { return desktopTasksController.map(Lazy::get); } return Optional.empty(); } @BindsOptionalOf @DynamicOverride abstract DesktopModeTaskRepository optionalDesktopModeTaskRepository(); Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +21 −2 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformComponents; import com.android.wm.shell.freeform.FreeformTaskListener; Loading Loading @@ -189,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 @@ -197,7 +199,8 @@ public abstract class WMShellModule { taskOrganizer, displayController, syncQueue, desktopModeController); desktopModeController, desktopTasksController); } // Loading Loading @@ -613,6 +616,22 @@ public abstract class WMShellModule { mainExecutor); } @WMSingleton @Provides @DynamicOverride static DesktopTasksController provideDesktopTasksController( Context context, ShellInit shellInit, ShellController shellController, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions, @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, @ShellMainThread ShellExecutor mainExecutor ) { return new DesktopTasksController(context, shellInit, shellController, shellTaskOrganizer, transitions, desktopModeTaskRepository, mainExecutor); } @WMSingleton @Provides @DynamicOverride Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java +5 −1 Original line number Diff line number Diff line Loading @@ -70,9 +70,13 @@ public class DesktopModeStatus { * @return {@code true} if active */ public static boolean isActive(Context context) { if (!IS_SUPPORTED) { if (!isAnyEnabled()) { return false; } if (isProto2Enabled()) { // Desktop mode is always active in prototype 2 return true; } try { int result = Settings.System.getIntForUser(context.getContentResolver(), Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT); Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt 0 → 100644 +231 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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 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 import androidx.annotation.BinderThread import com.android.internal.protolog.common.ProtoLog import com.android.wm.shell.ShellTaskOrganizer 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.annotations.ExternalThread import com.android.wm.shell.common.annotations.ShellMainThread import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.sysui.ShellSharedConstants import com.android.wm.shell.transition.Transitions import java.util.concurrent.Executor import java.util.function.Consumer /** Handles moving tasks in and out of desktop */ class DesktopTasksController( private val context: Context, shellInit: ShellInit, private val shellController: ShellController, private val shellTaskOrganizer: ShellTaskOrganizer, private val transitions: Transitions, private val desktopModeTaskRepository: DesktopModeTaskRepository, @ShellMainThread private val mainExecutor: ShellExecutor ) : RemoteCallable<DesktopTasksController> { private val desktopMode: DesktopModeImpl init { desktopMode = DesktopModeImpl() if (DesktopModeStatus.isProto2Enabled()) { shellInit.addInitCallback({ onInit() }, this) } } private fun onInit() { ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController") shellController.addExternalInterface( ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE, { createExternalInterface() }, this ) } /** Show all tasks, that are part of the desktop, on top of launcher */ fun showDesktopApps() { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "showDesktopApps") val wct = WindowContainerTransaction() bringDesktopAppsToFront(wct) // Execute transaction if there are pending operations if (!wct.isEmpty) { if (Transitions.ENABLE_SHELL_TRANSITIONS) { transitions.startTransition(WindowManager.TRANSIT_TO_FRONT, wct, null /* handler */) } else { shellTaskOrganizer.applyTransaction(wct) } } } /** 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() // Skip if all tasks are already visible if (activeTasks.isNotEmpty() && activeTasks.all(desktopModeTaskRepository::isVisibleTask)) { ProtoLog.d( WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: active tasks are already in front, skipping." ) return } ProtoLog.v(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront") // First move home to front and then other tasks on top of it moveHomeTaskToFront(wct) val allTasksInZOrder = desktopModeTaskRepository.getFreeformTasksInZOrder() activeTasks // Sort descending as the top task is at index 0. It should be ordered to top last .sortedByDescending { taskId -> allTasksInZOrder.indexOf(taskId) } .mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) } .forEach { task -> wct.reorder(task.token, true /* onTop */) } } private fun moveHomeTaskToFront(wct: WindowContainerTransaction) { shellTaskOrganizer .getRunningTasks(context.displayId) .firstOrNull { task -> task.activityType == ACTIVITY_TYPE_HOME } ?.let { homeTask -> wct.reorder(homeTask.getToken(), true /* onTop */) } } override fun getContext(): Context { return context } override fun getRemoteCallExecutor(): ShellExecutor { return mainExecutor } /** Creates a new instance of the external interface to pass to another process. */ private fun createExternalInterface(): ExternalInterfaceBinder { return IDesktopModeImpl(this) } /** Get connection interface between sysui and shell */ fun asDesktopMode(): DesktopMode { return desktopMode } /** * Adds a listener to find out about changes in the visibility of freeform tasks. * * @param listener the listener to add. * @param callbackExecutor the executor to call the listener on. */ fun addListener(listener: VisibleTasksListener, callbackExecutor: Executor) { desktopModeTaskRepository.addVisibleTasksListener(listener, callbackExecutor) } /** The interface for calls from outside the shell, within the host process. */ @ExternalThread private inner class DesktopModeImpl : DesktopMode { override fun addListener(listener: VisibleTasksListener, callbackExecutor: Executor) { mainExecutor.execute { this@DesktopTasksController.addListener(listener, callbackExecutor) } } } /** The interface for calls from outside the host process. */ @BinderThread private class IDesktopModeImpl(private var controller: DesktopTasksController?) : IDesktopMode.Stub(), ExternalInterfaceBinder { /** Invalidates this instance, preventing future calls from updating the controller. */ override fun invalidate() { controller = null } override fun showDesktopApps() { ExecutorUtils.executeRemoteCallWithTaskPermission( controller, "showDesktopApps", Consumer(DesktopTasksController::showDesktopApps) ) } } } libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +70 −15 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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<>(); Loading @@ -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, Loading @@ -100,6 +103,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { displayController, syncQueue, desktopModeController, desktopTasksController, new CaptionWindowDecoration.Factory(), InputManager::getInstance); } Loading @@ -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) { Loading @@ -123,6 +128,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mDisplayController = displayController; mSyncQueue = syncQueue; mDesktopModeController = desktopModeController; mDesktopTasksController = desktopTasksController; mCaptionWindowDecorFactory = captionWindowDecorFactory; mInputManagerSupplier = inputManagerSupplier; Loading Loading @@ -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); } } Loading Loading @@ -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; } Loading @@ -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; } Loading Loading @@ -420,15 +444,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 @@ -455,10 +493,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 @@ -472,7 +521,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 Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +23 −1 Original line number Diff line number Diff line Loading @@ -61,6 +61,7 @@ import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.displayareahelper.DisplayAreaHelperController; import com.android.wm.shell.draganddrop.DragAndDropController; Loading Loading @@ -677,7 +678,11 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static Optional<DesktopMode> provideDesktopMode( Optional<DesktopModeController> desktopModeController) { Optional<DesktopModeController> desktopModeController, Optional<DesktopTasksController> desktopTasksController) { if (DesktopModeStatus.isProto2Enabled()) { return desktopTasksController.map(DesktopTasksController::asDesktopMode); } return desktopModeController.map(DesktopModeController::asDesktopMode); } Loading @@ -698,6 +703,23 @@ public abstract class WMShellBaseModule { return Optional.empty(); } @BindsOptionalOf @DynamicOverride abstract DesktopTasksController optionalDesktopTasksController(); @WMSingleton @Provides static Optional<DesktopTasksController> providesDesktopTasksController( @DynamicOverride Optional<Lazy<DesktopTasksController>> desktopTasksController) { // Use optional-of-lazy for the dependency that this provider relies on. // Lazy ensures that this provider will not be the cause the dependency is created // when it will not be returned due to the condition below. if (DesktopModeStatus.isProto2Enabled()) { return desktopTasksController.map(Lazy::get); } return Optional.empty(); } @BindsOptionalOf @DynamicOverride abstract DesktopModeTaskRepository optionalDesktopModeTaskRepository(); Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +21 −2 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformComponents; import com.android.wm.shell.freeform.FreeformTaskListener; Loading Loading @@ -189,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 @@ -197,7 +199,8 @@ public abstract class WMShellModule { taskOrganizer, displayController, syncQueue, desktopModeController); desktopModeController, desktopTasksController); } // Loading Loading @@ -613,6 +616,22 @@ public abstract class WMShellModule { mainExecutor); } @WMSingleton @Provides @DynamicOverride static DesktopTasksController provideDesktopTasksController( Context context, ShellInit shellInit, ShellController shellController, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions, @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, @ShellMainThread ShellExecutor mainExecutor ) { return new DesktopTasksController(context, shellInit, shellController, shellTaskOrganizer, transitions, desktopModeTaskRepository, mainExecutor); } @WMSingleton @Provides @DynamicOverride Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java +5 −1 Original line number Diff line number Diff line Loading @@ -70,9 +70,13 @@ public class DesktopModeStatus { * @return {@code true} if active */ public static boolean isActive(Context context) { if (!IS_SUPPORTED) { if (!isAnyEnabled()) { return false; } if (isProto2Enabled()) { // Desktop mode is always active in prototype 2 return true; } try { int result = Settings.System.getIntForUser(context.getContentResolver(), Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT); Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt 0 → 100644 +231 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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 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 import androidx.annotation.BinderThread import com.android.internal.protolog.common.ProtoLog import com.android.wm.shell.ShellTaskOrganizer 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.annotations.ExternalThread import com.android.wm.shell.common.annotations.ShellMainThread import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.sysui.ShellSharedConstants import com.android.wm.shell.transition.Transitions import java.util.concurrent.Executor import java.util.function.Consumer /** Handles moving tasks in and out of desktop */ class DesktopTasksController( private val context: Context, shellInit: ShellInit, private val shellController: ShellController, private val shellTaskOrganizer: ShellTaskOrganizer, private val transitions: Transitions, private val desktopModeTaskRepository: DesktopModeTaskRepository, @ShellMainThread private val mainExecutor: ShellExecutor ) : RemoteCallable<DesktopTasksController> { private val desktopMode: DesktopModeImpl init { desktopMode = DesktopModeImpl() if (DesktopModeStatus.isProto2Enabled()) { shellInit.addInitCallback({ onInit() }, this) } } private fun onInit() { ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController") shellController.addExternalInterface( ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE, { createExternalInterface() }, this ) } /** Show all tasks, that are part of the desktop, on top of launcher */ fun showDesktopApps() { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "showDesktopApps") val wct = WindowContainerTransaction() bringDesktopAppsToFront(wct) // Execute transaction if there are pending operations if (!wct.isEmpty) { if (Transitions.ENABLE_SHELL_TRANSITIONS) { transitions.startTransition(WindowManager.TRANSIT_TO_FRONT, wct, null /* handler */) } else { shellTaskOrganizer.applyTransaction(wct) } } } /** 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() // Skip if all tasks are already visible if (activeTasks.isNotEmpty() && activeTasks.all(desktopModeTaskRepository::isVisibleTask)) { ProtoLog.d( WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: active tasks are already in front, skipping." ) return } ProtoLog.v(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront") // First move home to front and then other tasks on top of it moveHomeTaskToFront(wct) val allTasksInZOrder = desktopModeTaskRepository.getFreeformTasksInZOrder() activeTasks // Sort descending as the top task is at index 0. It should be ordered to top last .sortedByDescending { taskId -> allTasksInZOrder.indexOf(taskId) } .mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) } .forEach { task -> wct.reorder(task.token, true /* onTop */) } } private fun moveHomeTaskToFront(wct: WindowContainerTransaction) { shellTaskOrganizer .getRunningTasks(context.displayId) .firstOrNull { task -> task.activityType == ACTIVITY_TYPE_HOME } ?.let { homeTask -> wct.reorder(homeTask.getToken(), true /* onTop */) } } override fun getContext(): Context { return context } override fun getRemoteCallExecutor(): ShellExecutor { return mainExecutor } /** Creates a new instance of the external interface to pass to another process. */ private fun createExternalInterface(): ExternalInterfaceBinder { return IDesktopModeImpl(this) } /** Get connection interface between sysui and shell */ fun asDesktopMode(): DesktopMode { return desktopMode } /** * Adds a listener to find out about changes in the visibility of freeform tasks. * * @param listener the listener to add. * @param callbackExecutor the executor to call the listener on. */ fun addListener(listener: VisibleTasksListener, callbackExecutor: Executor) { desktopModeTaskRepository.addVisibleTasksListener(listener, callbackExecutor) } /** The interface for calls from outside the shell, within the host process. */ @ExternalThread private inner class DesktopModeImpl : DesktopMode { override fun addListener(listener: VisibleTasksListener, callbackExecutor: Executor) { mainExecutor.execute { this@DesktopTasksController.addListener(listener, callbackExecutor) } } } /** The interface for calls from outside the host process. */ @BinderThread private class IDesktopModeImpl(private var controller: DesktopTasksController?) : IDesktopMode.Stub(), ExternalInterfaceBinder { /** Invalidates this instance, preventing future calls from updating the controller. */ override fun invalidate() { controller = null } override fun showDesktopApps() { ExecutorUtils.executeRemoteCallWithTaskPermission( controller, "showDesktopApps", Consumer(DesktopTasksController::showDesktopApps) ) } } }
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +70 −15 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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<>(); Loading @@ -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, Loading @@ -100,6 +103,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { displayController, syncQueue, desktopModeController, desktopTasksController, new CaptionWindowDecoration.Factory(), InputManager::getInstance); } Loading @@ -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) { Loading @@ -123,6 +128,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mDisplayController = displayController; mSyncQueue = syncQueue; mDesktopModeController = desktopModeController; mDesktopTasksController = desktopTasksController; mCaptionWindowDecorFactory = captionWindowDecorFactory; mInputManagerSupplier = inputManagerSupplier; Loading Loading @@ -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); } } Loading Loading @@ -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; } Loading @@ -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; } Loading Loading @@ -420,15 +444,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 @@ -455,10 +493,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 @@ -472,7 +521,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