Skip to content
Snippets Groups Projects
Commit accc7f1d authored by Winson Chung's avatar Winson Chung
Browse files

Add basic implementation of starting an intent on an unhandled drag

- Expose drags through DragAndDropController, DesktopModeController will
  listen for unhandled drag events and consume it to start the associated
  task

Bug: 320797628
Test: atest WMShellUnitTests
Change-Id: I9d114f58a3224299f3ccb060543533b6fbf4c9ff
parent f942b9ad
No related branches found
No related tags found
No related merge requests found
......@@ -481,7 +481,12 @@ public class BubbleController implements ConfigurationChangeListener,
});
mOneHandedOptional.ifPresent(this::registerOneHandedState);
mDragAndDropController.addListener(this::collapseStack);
mDragAndDropController.addListener(new DragAndDropController.DragAndDropListener() {
@Override
public void onDragStarted() {
collapseStack();
}
});
// Clear out any persisted bubbles on disk that no longer have a valid user.
List<UserInfo> users = mUserManager.getAliveUsers();
......
......@@ -498,6 +498,7 @@ public abstract class WMShellModule {
ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
DragAndDropController dragAndDropController,
Transitions transitions,
EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
......@@ -506,14 +507,15 @@ public abstract class WMShellModule {
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
LaunchAdjacentController launchAdjacentController,
RecentsTransitionHandler recentsTransitionHandler,
MultiInstanceHelper multiInstanceHelper,
@ShellMainThread ShellExecutor mainExecutor
) {
return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler,
toggleResizeDesktopTaskTransitionHandler, dragToDesktopTransitionHandler,
desktopModeTaskRepository, launchAdjacentController, recentsTransitionHandler,
mainExecutor);
dragAndDropController, transitions, enterDesktopTransitionHandler,
exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler,
dragToDesktopTransitionHandler, desktopModeTaskRepository, launchAdjacentController,
recentsTransitionHandler, multiInstanceHelper, mainExecutor);
}
@WMSingleton
......
......@@ -16,7 +16,10 @@
package com.android.wm.shell.desktopmode
import android.app.ActivityManager
import android.app.ActivityManager.RunningTaskInfo
import android.app.ActivityOptions
import android.app.PendingIntent
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
......@@ -25,6 +28,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
import android.content.Intent
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
......@@ -32,6 +36,7 @@ import android.graphics.Region
import android.os.IBinder
import android.os.SystemProperties
import android.util.DisplayMetrics.DENSITY_DEFAULT
import android.view.Display.DEFAULT_DISPLAY
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_NONE
......@@ -49,6 +54,8 @@ import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.ExecutorUtils
import com.android.wm.shell.common.ExternalInterfaceBinder
import com.android.wm.shell.common.LaunchAdjacentController
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.MultiInstanceHelper.Companion.getComponent
import com.android.wm.shell.common.RemoteCallable
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SingleInstanceRemoteListener
......@@ -59,7 +66,9 @@ import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOT
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.recents.RecentTasksController
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.splitscreen.SplitScreenController
......@@ -76,6 +85,7 @@ import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.function.Consumer
import java.util.function.Function
/** Handles moving tasks in and out of desktop */
class DesktopTasksController(
......@@ -87,6 +97,7 @@ class DesktopTasksController(
private val shellTaskOrganizer: ShellTaskOrganizer,
private val syncQueue: SyncTransactionQueue,
private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
private val dragAndDropController: DragAndDropController,
private val transitions: Transitions,
private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
......@@ -96,8 +107,10 @@ class DesktopTasksController(
private val desktopModeTaskRepository: DesktopModeTaskRepository,
private val launchAdjacentController: LaunchAdjacentController,
private val recentsTransitionHandler: RecentsTransitionHandler,
private val multiInstanceHelper: MultiInstanceHelper,
@ShellMainThread private val mainExecutor: ShellExecutor
) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler {
) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler,
DragAndDropController.DragAndDropListener {
private val desktopMode: DesktopModeImpl
private var visualIndicator: DesktopModeVisualIndicator? = null
......@@ -174,6 +187,7 @@ class DesktopTasksController(
}
}
)
dragAndDropController.addListener(this)
}
fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
......@@ -1023,6 +1037,50 @@ class DesktopTasksController(
desktopModeTaskRepository.setExclusionRegionListener(listener, callbackExecutor)
}
override fun onUnhandledDrag(
launchIntent: PendingIntent,
dragSurface: SurfaceControl,
onFinishCallback: Consumer<Boolean>
): Boolean {
// TODO(b/320797628): Pass through which display we are dropping onto
val activeTasks = desktopModeTaskRepository.getActiveTasks(DEFAULT_DISPLAY)
if (!activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
// Not currently in desktop mode, ignore the drop
return false
}
val launchComponent = getComponent(launchIntent)
if (!multiInstanceHelper.supportsMultiInstanceSplit(launchComponent)) {
// TODO(b/320797628): Should only return early if there is an existing running task, and
// notify the user as well. But for now, just ignore the drop.
KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "Dropped intent does not support multi-instance")
return false
}
// Start a new transition to launch the app
val opts = ActivityOptions.makeBasic().apply {
launchWindowingMode = WINDOWING_MODE_FREEFORM
pendingIntentLaunchFlags =
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
}
val wct = WindowContainerTransaction()
wct.sendPendingIntent(launchIntent, null, opts.toBundle())
transitions.startTransition(TRANSIT_OPEN, wct, null /* handler */)
// Report that this is handled by the listener
onFinishCallback.accept(true)
// We've assumed responsibility of cleaning up the drag surface, so do that now
// TODO(b/320797628): Do an actual animation here for the drag surface
val t = SurfaceControl.Transaction()
t.remove(dragSurface)
t.apply()
return true
}
private fun dump(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
pw.println("${prefix}DesktopTasksController")
......
......@@ -37,6 +37,7 @@ import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DR
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.content.ClipDescription;
import android.content.ComponentCallbacks2;
import android.content.Context;
......@@ -77,6 +78,8 @@ import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Handles the global drag and drop handling for the Shell.
......@@ -103,12 +106,29 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
// Map of displayId -> per-display info
private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>();
// The current display if a drag is in progress
private int mActiveDragDisplay = -1;
/**
* Listener called during drag events, currently just onDragStarted.
* Listener called during drag events.
*/
public interface DragAndDropListener {
/** Called when a drag has started. */
void onDragStarted();
default void onDragStarted() {}
/** Called when a drag has ended. */
default void onDragEnded() {}
/**
* Called when an unhandled drag has occurred. The impl must return true if it decides to
* handled the unhandled drag, and it must also call `onFinishCallback` to complete the
* drag.
*/
default boolean onUnhandledDrag(@NonNull PendingIntent launchIntent,
@NonNull SurfaceControl dragSurface,
@NonNull Consumer<Boolean> onFinishCallback) {
return false;
}
}
public DragAndDropController(Context context,
......@@ -180,10 +200,18 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
mListeners.remove(listener);
}
private void notifyDragStarted() {
/**
* Notifies all listeners and returns whether any listener handled the callback.
*/
private boolean notifyListeners(Function<DragAndDropListener, Boolean> callback) {
for (int i = 0; i < mListeners.size(); i++) {
mListeners.get(i).onDragStarted();
boolean handled = callback.apply(mListeners.get(i));
if (handled) {
// Return once the callback reports it has handled it
return true;
}
}
return false;
}
@Override
......@@ -269,6 +297,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
}
if (event.getAction() == ACTION_DRAG_STARTED) {
mActiveDragDisplay = displayId;
pd.isHandlingDrag = DragUtils.canHandleDrag(event);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
"Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s",
......@@ -294,7 +323,11 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
pd.dragSession.update();
pd.dragLayout.prepare(pd.dragSession, loggerSessionId);
setDropTargetWindowVisibility(pd, View.VISIBLE);
notifyDragStarted();
notifyListeners(l -> {
l.onDragStarted();
// Return false to continue dispatch to next listener
return false;
});
break;
case ACTION_DRAG_ENTERED:
pd.dragLayout.show();
......@@ -328,6 +361,12 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
});
}
mLogger.logEnd();
mActiveDragDisplay = -1;
notifyListeners(l -> {
l.onDragEnded();
// Return false to continue dispatch to next listener
return false;
});
break;
}
return true;
......@@ -341,6 +380,24 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
mTransitions.startTransition(WindowManager.TRANSIT_TO_FRONT, wct, null);
}
@Override
public void onUnhandledDrop(@NonNull DragEvent dragEvent,
@NonNull Consumer<Boolean> onFinishCallback) {
final PendingIntent launchIntent = DragUtils.getLaunchIntent(dragEvent);
if (launchIntent == null) {
// No intent to launch, report that this is unhandled by the listener
onFinishCallback.accept(false);
return;
}
final boolean handled = notifyListeners(
l -> l.onUnhandledDrag(launchIntent, dragEvent.getDragSurface(), onFinishCallback));
if (!handled) {
// Nobody handled this, we still have to notify WM
onFinishCallback.accept(false);
}
}
/**
* Handles dropping on the drop target.
*/
......
......@@ -20,9 +20,14 @@ import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipDescription;
import android.view.DragEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/** Collection of utility classes for handling drag and drop. */
public class DragUtils {
private static final String TAG = "DragUtils";
......@@ -44,6 +49,31 @@ public class DragUtils {
|| description.hasMimeType(MIMETYPE_APPLICATION_TASK);
}
/**
* Returns a launchable intent in the given `DragEvent` or `null` if there is none.
*/
@Nullable
public static PendingIntent getLaunchIntent(@NonNull DragEvent dragEvent) {
return getLaunchIntent(dragEvent.getClipData());
}
/**
* Returns a launchable intent in the given `ClipData` or `null` if there is none.
*/
@Nullable
public static PendingIntent getLaunchIntent(@NonNull ClipData data) {
for (int i = 0; i < data.getItemCount(); i++) {
final ClipData.Item item = data.getItemAt(i);
if (item.getIntentSender() != null) {
final PendingIntent intent = new PendingIntent(item.getIntentSender().getTarget());
if (intent != null && intent.isActivity()) {
return intent;
}
}
}
return null;
}
/**
* Returns a list of the mime types provided in the clip description.
*/
......
......@@ -30,6 +30,7 @@ import java.util.function.Consumer
/**
* Manages the listener and callbacks for unhandled global drags.
* This is only used by DragAndDropController and should not be used directly by other classes.
*/
class GlobalDragListener(
private val wmService: IWindowManager,
......
......@@ -48,12 +48,14 @@ import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.LaunchAdjacentController
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
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.desktopmode.DesktopTestHelpers.Companion.createSplitScreenTask
import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.splitscreen.SplitScreenController
......@@ -106,6 +108,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Mock lateinit var desktopModeWindowDecoration: DesktopModeWindowDecoration
@Mock lateinit var splitScreenController: SplitScreenController
@Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler
@Mock lateinit var dragAndDropController: DragAndDropController
@Mock lateinit var multiInstanceHelper: MultiInstanceHelper
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
......@@ -148,6 +152,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
shellTaskOrganizer,
syncQueue,
rootTaskDisplayAreaOrganizer,
dragAndDropController,
transitions,
enterDesktopTransitionHandler,
exitDesktopTransitionHandler,
......@@ -156,6 +161,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
desktopModeTaskRepository,
launchAdjacentController,
recentsTransitionHandler,
multiInstanceHelper,
shellExecutor
)
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment