diff --git a/core/java/android/window/IGlobalDragListener.aidl b/core/java/android/window/IGlobalDragListener.aidl index 81388559f8dc6870362d437e94e4c8f1539effa4..8f2ca02b3c09f9e875ecceb1b85fb7f59eab3882 100644 --- a/core/java/android/window/IGlobalDragListener.aidl +++ b/core/java/android/window/IGlobalDragListener.aidl @@ -16,6 +16,7 @@ package android.window; +import android.app.ActivityManager; import android.view.DragEvent; import android.window.IUnhandledDragCallback; @@ -24,6 +25,12 @@ import android.window.IUnhandledDragCallback; * {@hide} */ oneway interface IGlobalDragListener { + /** + * Called when a cross-window drag is handled by another window. + * @param taskInfo the task containing the window that consumed the drop + */ + void onCrossWindowDrop(in ActivityManager.RunningTaskInfo taskInfo); + /** * Called when the user finishes the drag gesture but no windows have reported handling the * drop. The DragEvent is populated with the drag surface for the listener to animate. The diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index fe65fdd30e48349ec194eb6a8aa9a7cb7443cfc7..d8d0d876b4f2dac86309a0dd1f084da32bd3e765 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -643,19 +643,6 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } } - /** Helper to set int metadata on the Surface corresponding to the task id. */ - public void setSurfaceMetadata(int taskId, int key, int value) { - synchronized (mLock) { - final TaskAppearedInfo info = mTasks.get(taskId); - if (info == null || info.getLeash() == null) { - return; - } - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - t.setMetadata(info.getLeash(), key, value); - t.apply(); - } - } - private boolean updateTaskListenerIfNeeded(RunningTaskInfo taskInfo, SurfaceControl leash, TaskListener oldListener, TaskListener newListener) { if (oldListener == newListener) return false; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 34359a523cd8974802ec84a0f734dad2507507af..aded1153d778647d0000ade6c3e54dea173764ea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -577,9 +577,12 @@ public abstract class WMShellModule { DisplayController displayController, UiEventLogger uiEventLogger, IconProvider iconProvider, + GlobalDragListener globalDragListener, + Transitions transitions, @ShellMainThread ShellExecutor mainExecutor) { - return new DragAndDropController(context, shellInit, shellController, - shellCommandHandler, displayController, uiEventLogger, iconProvider, mainExecutor); + return new DragAndDropController(context, shellInit, shellController, shellCommandHandler, + displayController, uiEventLogger, iconProvider, globalDragListener, transitions, + mainExecutor); } // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java index 269c3699ac0a083c28c32edc4b7e78376d17e70d..0fea3e3e219c0ba87f3e5aa385897ba64f544811 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java @@ -35,6 +35,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP; +import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.content.ClipDescription; import android.content.ComponentCallbacks2; @@ -51,6 +52,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.FrameLayout; +import android.window.WindowContainerTransaction; import androidx.annotation.BinderThread; import androidx.annotation.NonNull; @@ -71,6 +73,7 @@ import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; import java.util.ArrayList; @@ -79,6 +82,7 @@ import java.util.ArrayList; * Handles the global drag and drop handling for the Shell. */ public class DragAndDropController implements RemoteCallable<DragAndDropController>, + GlobalDragListener.GlobalDragListenerCallback, DisplayController.OnDisplaysChangedListener, View.OnDragListener, ComponentCallbacks2 { @@ -90,6 +94,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll private final DisplayController mDisplayController; private final DragAndDropEventLogger mLogger; private final IconProvider mIconProvider; + private final GlobalDragListener mGlobalDragListener; + private final Transitions mTransitions; private SplitScreenController mSplitScreen; private ShellExecutor mMainExecutor; private ArrayList<DragAndDropListener> mListeners = new ArrayList<>(); @@ -112,6 +118,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll DisplayController displayController, UiEventLogger uiEventLogger, IconProvider iconProvider, + GlobalDragListener globalDragListener, + Transitions transitions, ShellExecutor mainExecutor) { mContext = context; mShellController = shellController; @@ -119,6 +127,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll mDisplayController = displayController; mLogger = new DragAndDropEventLogger(uiEventLogger); mIconProvider = iconProvider; + mGlobalDragListener = globalDragListener; + mTransitions = transitions; mMainExecutor = mainExecutor; shellInit.addInitCallback(this::onInit, this); } @@ -136,6 +146,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll mShellController.addExternalInterface(KEY_EXTRA_SHELL_DRAG_AND_DROP, this::createExternalInterface, this); mShellCommandHandler.addDumpCallback(this::dump, this); + mGlobalDragListener.setListener(this); } private ExternalInterfaceBinder createExternalInterface() { @@ -322,6 +333,14 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll return true; } + @Override + public void onCrossWindowDrop(@NonNull ActivityManager.RunningTaskInfo taskInfo) { + // Bring the task forward when an item is dropped on it + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.reorder(taskInfo.token, true /* onTop */); + mTransitions.startTransition(WindowManager.TRANSIT_TO_FRONT, wct, null); + } + /** * Handles dropping on the drop target. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt index eb352fcf1a8ff52a426e3ef863cc5e0df0639fcc..7f7cfb964e7db1628efd1c42367617173059d006 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt @@ -15,6 +15,7 @@ */ package com.android.wm.shell.draganddrop +import android.app.ActivityManager import android.os.RemoteException import android.util.Log import android.view.DragEvent @@ -31,13 +32,19 @@ import java.util.function.Consumer * Manages the listener and callbacks for unhandled global drags. */ class GlobalDragListener( - val wmService: IWindowManager, - mainExecutor: ShellExecutor + private val wmService: IWindowManager, + private val mainExecutor: ShellExecutor ) { private var callback: GlobalDragListenerCallback? = null private val globalDragListener: IGlobalDragListener = object : IGlobalDragListener.Stub() { + override fun onCrossWindowDrop(taskInfo: ActivityManager.RunningTaskInfo) { + mainExecutor.execute() { + this@GlobalDragListener.onCrossWindowDrop(taskInfo) + } + } + override fun onUnhandledDrop(event: DragEvent, callback: IUnhandledDragCallback) { mainExecutor.execute() { this@GlobalDragListener.onUnhandledDrop(event, callback) @@ -49,6 +56,11 @@ class GlobalDragListener( * Callbacks for global drag events. */ interface GlobalDragListenerCallback { + /** + * Called when a global drag is successfully handled by another window. + */ + fun onCrossWindowDrop(taskInfo: ActivityManager.RunningTaskInfo) {} + /** * Called when a global drag is unhandled (ie. dropped outside of all visible windows, or * dropped on a window that does not want to handle it). @@ -79,12 +91,20 @@ class GlobalDragListener( } } + @VisibleForTesting + fun onCrossWindowDrop(taskInfo: ActivityManager.RunningTaskInfo) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "onCrossWindowDrop: %s", taskInfo) + callback?.onCrossWindowDrop(taskInfo) + } + @VisibleForTesting fun onUnhandledDrop(dragEvent: DragEvent, wmCallback: IUnhandledDragCallback) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "onUnhandledDrop: %s", dragEvent) if (callback == null) { wmCallback.notifyUnhandledDropComplete(false) + return } callback?.onUnhandledDrop(dragEvent) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 5e79681e060b4ea836bf7a4197936a1ff0fe4676..8a0e7977178c52496f90e0a2f89d363ee54c4a33 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -1170,7 +1170,11 @@ public class Transitions implements RemoteCallable<Transitions>, mPendingTransitions.add(0, active); } - /** Start a new transition directly. */ + /** + * Start a new transition directly. + * @param handler if null, the transition will be dispatched to the registered set of transition + * handlers to be handled + */ public IBinder startTransition(@WindowManager.TransitionType int type, @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Directly starting a new transition " diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java index 54f36f61859d5ea74d7b230b5b04c2ea49561f45..a64ebd301c002e7b4284aa36321daedf182dc73c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java @@ -45,12 +45,14 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; import com.android.launcher3.icons.IconProvider; +import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; import org.junit.Before; import org.junit.Test; @@ -84,7 +86,9 @@ public class DragAndDropControllerTest extends ShellTestCase { @Mock private ShellExecutor mMainExecutor; @Mock - private WindowManager mWindowManager; + private Transitions mTransitions; + @Mock + private GlobalDragListener mGlobalDragListener; private DragAndDropController mController; @@ -93,7 +97,7 @@ public class DragAndDropControllerTest extends ShellTestCase { MockitoAnnotations.initMocks(this); mController = new DragAndDropController(mContext, mShellInit, mShellController, mShellCommandHandler, mDisplayController, mUiEventLogger, mIconProvider, - mMainExecutor); + mGlobalDragListener, mTransitions, mMainExecutor); mController.onInit(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt index 4c025984e095e5616e0922e5731db35a10049803..e731b06c0947d0280976f642877d43733fb49a04 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt @@ -15,6 +15,7 @@ */ package com.android.wm.shell.draganddrop +import android.app.ActivityManager.RunningTaskInfo import android.os.RemoteException import android.view.DragEvent import android.view.DragEvent.ACTION_DROP @@ -32,12 +33,10 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers -import org.mockito.Mock import org.mockito.Mockito -import org.mockito.Mockito.mock -import org.mockito.Mockito.reset -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations +import org.mockito.kotlin.mock +import org.mockito.kotlin.reset +import org.mockito.kotlin.verify /** * Tests for the unhandled drag controller. @@ -45,18 +44,14 @@ import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidJUnit4::class) class UnhandledDragControllerTest : ShellTestCase() { - @Mock - private lateinit var mIWindowManager: IWindowManager - - @Mock - private lateinit var mMainExecutor: ShellExecutor + private val mIWindowManager = mock<IWindowManager>() + private val mMainExecutor = mock<ShellExecutor>() private lateinit var mController: GlobalDragListener @Before @Throws(RemoteException::class) fun setUp() { - MockitoAnnotations.initMocks(this) mController = GlobalDragListener(mIWindowManager, mMainExecutor) } @@ -81,7 +76,7 @@ class UnhandledDragControllerTest : ShellTestCase() { // Simulate an unhandled drop val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null, null, null, false) - val wmCallback = mock(IUnhandledDragCallback::class.java) + val wmCallback = mock<IUnhandledDragCallback>() mController.onUnhandledDrop(dropEvent, wmCallback) verify(wmCallback).notifyUnhandledDropComplete(ArgumentMatchers.eq(false)) @@ -102,14 +97,31 @@ class UnhandledDragControllerTest : ShellTestCase() { }) // Simulate an unhandled drop - val dragSurface = mock(SurfaceControl::class.java) + val dragSurface = mock<SurfaceControl>() val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null, dragSurface, null, false) - val wmCallback = mock(IUnhandledDragCallback::class.java) + val wmCallback = mock<IUnhandledDragCallback>() mController.onUnhandledDrop(dropEvent, wmCallback) verify(wmCallback).notifyUnhandledDropComplete(ArgumentMatchers.eq(true)) verify(dragSurface).release() assertEquals(lastDragEvent.get(0), dropEvent) } + + @Test + fun onCrossWindowDrop() { + val lastTaskInfo = arrayOfNulls<RunningTaskInfo>(1) + + // Set a listener to listen for unhandled drops + mController.setListener(object : GlobalDragListenerCallback { + override fun onCrossWindowDrop(taskInfo: RunningTaskInfo) { + lastTaskInfo[0] = taskInfo + } + }) + + // Simulate a cross-window drop + val taskInfo = mock<RunningTaskInfo>() + mController.onCrossWindowDrop(taskInfo) + assertEquals(lastTaskInfo.get(0), taskInfo) + } } diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java index a83c25feb236397735dd4d2db4aa1fb12f98d7ee..b68e67e291e76889ce1d06501c8e682da7a13511 100644 --- a/services/core/java/com/android/server/wm/DragDropController.java +++ b/services/core/java/com/android/server/wm/DragDropController.java @@ -25,6 +25,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACT import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; +import android.app.ActivityManager; import android.content.ClipData; import android.content.Context; import android.hardware.input.InputManagerGlobal; @@ -344,7 +345,20 @@ class DragDropController { final boolean relinquishDragSurfaceToDropTarget = consumed && mDragState.targetInterceptsGlobalDrag(callingWin); + final boolean isCrossWindowDrag = !mDragState.mLocalWin.equals(token); mDragState.endDragLocked(consumed, relinquishDragSurfaceToDropTarget); + + final Task droppedWindowTask = callingWin.getTask(); + if (com.android.window.flags.Flags.delegateUnhandledDrags() + && mGlobalDragListener != null && droppedWindowTask != null && consumed + && isCrossWindowDrag) { + try { + mGlobalDragListener.onCrossWindowDrop(droppedWindowTask.getTaskInfo()); + } catch (RemoteException e) { + Slog.e(TAG_WM, "Failed to call global drag listener for cross-window " + + "drop", e); + } + } } } finally { mCallback.get().postReportDropResult(); @@ -383,7 +397,7 @@ class DragDropController { }); return true; } catch (RemoteException e) { - Slog.e(TAG_WM, "Failed to call unhandled drag listener", e); + Slog.e(TAG_WM, "Failed to call global drag listener for unhandled drop", e); return false; } }