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

Commit 18dfbc05 authored by Winson Chung's avatar Winson Chung Committed by Android (Google) Code Review
Browse files

Merge "Add basic implementation of starting an intent on an unhandled drag" into main

parents b5bf7f2b accc7f1d
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