Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -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();
+6 −4
Original line number Diff line number Diff line
@@ -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
+59 −1
Original line number Diff line number Diff line
@@ -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")
+62 −5
Original line number Diff line number Diff line
@@ -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.
     */
+30 −0
Original line number Diff line number Diff line
@@ -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.
     */
Loading