Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +6 −1 Original line number Diff line number Diff line Loading @@ -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(); Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +6 −4 Original line number Diff line number Diff line Loading @@ -498,6 +498,7 @@ public abstract class WMShellModule { ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, DragAndDropController dragAndDropController, Transitions transitions, EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler, ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler, Loading @@ -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 Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +59 −1 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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( Loading @@ -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, Loading @@ -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 Loading Loading @@ -174,6 +187,7 @@ class DesktopTasksController( } } ) dragAndDropController.addListener(this) } fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) { Loading Loading @@ -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") Loading libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java +62 −5 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading @@ -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, Loading Loading @@ -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 Loading Loading @@ -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", Loading @@ -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(); Loading Loading @@ -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; Loading @@ -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. */ Loading libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java +30 −0 Original line number Diff line number Diff line Loading @@ -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"; Loading @@ -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. */ Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +6 −1 Original line number Diff line number Diff line Loading @@ -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(); Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +6 −4 Original line number Diff line number Diff line Loading @@ -498,6 +498,7 @@ public abstract class WMShellModule { ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, DragAndDropController dragAndDropController, Transitions transitions, EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler, ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler, Loading @@ -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 Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +59 −1 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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( Loading @@ -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, Loading @@ -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 Loading Loading @@ -174,6 +187,7 @@ class DesktopTasksController( } } ) dragAndDropController.addListener(this) } fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) { Loading Loading @@ -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") Loading
libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java +62 −5 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading @@ -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, Loading Loading @@ -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 Loading Loading @@ -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", Loading @@ -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(); Loading Loading @@ -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; Loading @@ -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. */ Loading
libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java +30 −0 Original line number Diff line number Diff line Loading @@ -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"; Loading @@ -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. */ Loading