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

Commit f4ed441e authored by Winson Chung's avatar Winson Chung
Browse files

Allow dragging to launch multiple instances of the same activity

- When dragging to launch a new app, apply MULTIPLE_TASK to the launch
  intent to allow apps that support it to show side-by-side
- This requires the pending intent resolved from LauncherAppsService to
  be mutable (should be ok since we only use this api from SysUI)
- Also remove some unused members and duplication of getting the running
  task when dragging

Bug: 207686016
Test: atest WMShellUnitTests

Change-Id: Ib233ad754a6c6e3c4e0d0e10ed788ab8e055cccc
parent 5f32b96c
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -58,7 +58,7 @@ interface ILauncherApps {
    void startActivityAsUser(in IApplicationThread caller, String callingPackage,
            String callingFeatureId, in ComponentName component, in Rect sourceBounds,
            in Bundle opts, in UserHandle user);
    PendingIntent getActivityLaunchIntent(in ComponentName component, in Bundle opts,
    PendingIntent getActivityLaunchIntent(String callingPackage, in ComponentName component,
            in UserHandle user);
    void showAppDetailsAsUser(in IApplicationThread caller, String callingPackage,
            String callingFeatureId, in ComponentName component, in Rect sourceBounds,
+9 −4
Original line number Diff line number Diff line
@@ -749,24 +749,29 @@ public class LauncherApps {
    }

    /**
     * Returns a PendingIntent that would start the same activity started from
     * {@link #startMainActivity(ComponentName, UserHandle, Rect, Bundle)}.
     * Returns a mutable PendingIntent that would start the same activity started from
     * {@link #startMainActivity(ComponentName, UserHandle, Rect, Bundle)}.  The caller needs to
     * take care in ensuring that the mutable intent returned is not passed to untrusted parties.
     *
     * @param component The ComponentName of the activity to launch
     * @param startActivityOptions This parameter is no longer supported
     * @param user The UserHandle of the profile
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS)
    @Nullable
    public PendingIntent getMainActivityLaunchIntent(@NonNull ComponentName component,
            @Nullable Bundle startActivityOptions, @NonNull UserHandle user) {
        if (mContext.checkSelfPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS)
                != PackageManager.PERMISSION_GRANTED) {
            Log.w(TAG, "Only allowed for recents.");
        }
        logErrorForInvalidProfileAccess(user);
        if (DEBUG) {
            Log.i(TAG, "GetMainActivityLaunchIntent " + component + " " + user);
        }
        try {
            // due to b/209607104, startActivityOptions will be ignored
            return mService.getActivityLaunchIntent(component, null /* opts */, user);
            return mService.getActivityLaunchIntent(mContext.getPackageName(), component, user);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
+56 −10
Original line number Diff line number Diff line
@@ -45,10 +45,12 @@ import android.app.WindowConfiguration;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.ResolveInfo;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Bundle;
@@ -62,9 +64,11 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;

import java.lang.annotation.Retention;
@@ -106,11 +110,18 @@ public class DragAndDropPolicy {
     */
    void start(DisplayLayout displayLayout, ClipData data, InstanceId loggerSessionId) {
        mLoggerSessionId = loggerSessionId;
        mSession = new DragSession(mContext, mActivityTaskManager, displayLayout, data);
        mSession = new DragSession(mActivityTaskManager, displayLayout, data);
        // TODO(b/169894807): Also update the session data with task stack changes
        mSession.update();
    }

    /**
     * Returns the last running task.
     */
    ActivityManager.RunningTaskInfo getLatestRunningTask() {
        return mSession.runningTaskInfo;
    }

    /**
     * Returns the target's regions based on the current state of the device and display.
     */
@@ -248,32 +259,68 @@ public class DragAndDropPolicy {
            final UserHandle user = intent.getParcelableExtra(EXTRA_USER);
            mStarter.startShortcut(packageName, id, position, opts, user);
        } else {
            mStarter.startIntent(intent.getParcelableExtra(EXTRA_PENDING_INTENT),
                    null, position, opts);
            final PendingIntent launchIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
            mStarter.startIntent(launchIntent, getStartIntentFillInIntent(launchIntent, position),
                    position, opts);
        }
    }

    /**
     * Returns the fill-in intent to use when starting an app from a drop.
     */
    @VisibleForTesting
    Intent getStartIntentFillInIntent(PendingIntent launchIntent, @SplitPosition int position) {
        // Get the drag app
        final List<ResolveInfo> infos = launchIntent.queryIntentComponents(0 /* flags */);
        final ComponentName dragIntentActivity = !infos.isEmpty()
                ? infos.get(0).activityInfo.getComponentName()
                : null;

        // Get the current app (either fullscreen or the remaining app post-drop if in splitscreen)
        final boolean inSplitScreen = mSplitScreen != null
                && mSplitScreen.isSplitScreenVisible();
        final ComponentName currentActivity;
        if (!inSplitScreen) {
            currentActivity = mSession.runningTaskInfo != null
                    ? mSession.runningTaskInfo.baseActivity
                    : null;
        } else {
            final int nonReplacedSplitPosition = position == SPLIT_POSITION_TOP_OR_LEFT
                    ? SPLIT_POSITION_BOTTOM_OR_RIGHT
                    : SPLIT_POSITION_TOP_OR_LEFT;
            ActivityManager.RunningTaskInfo nonReplacedTaskInfo =
                    mSplitScreen.getTaskInfo(nonReplacedSplitPosition);
            currentActivity = nonReplacedTaskInfo.baseActivity;
        }

        if (currentActivity.equals(dragIntentActivity)) {
            // Only apply MULTIPLE_TASK if we are dragging the same activity
            final Intent fillInIntent = new Intent();
            fillInIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Adding MULTIPLE_TASK");
            return fillInIntent;
        }
        return null;
    }

    /**
     * Per-drag session data.
     */
    private static class DragSession {
        private final Context mContext;
        private final ActivityTaskManager mActivityTaskManager;
        private final ClipData mInitialDragData;

        final DisplayLayout displayLayout;
        Intent dragData;
        int runningTaskId;
        ActivityManager.RunningTaskInfo runningTaskInfo;
        @WindowConfiguration.WindowingMode
        int runningTaskWinMode = WINDOWING_MODE_UNDEFINED;
        @WindowConfiguration.ActivityType
        int runningTaskActType = ACTIVITY_TYPE_STANDARD;
        boolean runningTaskIsResizeable;
        boolean dragItemSupportsSplitscreen;

        DragSession(Context context, ActivityTaskManager activityTaskManager,
        DragSession(ActivityTaskManager activityTaskManager,
                DisplayLayout dispLayout, ClipData data) {
            mContext = context;
            mActivityTaskManager = activityTaskManager;
            mInitialDragData = data;
            displayLayout = dispLayout;
@@ -287,10 +334,9 @@ public class DragAndDropPolicy {
                    mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */);
            if (!tasks.isEmpty()) {
                final ActivityManager.RunningTaskInfo task = tasks.get(0);
                runningTaskInfo = task;
                runningTaskWinMode = task.getWindowingMode();
                runningTaskActType = task.getActivityType();
                runningTaskId = task.taskId;
                runningTaskIsResizeable = task.isResizeable;
            }

            final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo();
+2 −14
Original line number Diff line number Diff line
@@ -26,7 +26,6 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.StatusBarManager;
import android.content.ClipData;
import android.content.Context;
@@ -35,7 +34,6 @@ import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.RemoteException;
import android.view.DragEvent;
import android.view.SurfaceControl;
import android.view.WindowInsets;
@@ -51,7 +49,6 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;

import java.util.ArrayList;
import java.util.List;

/**
 * Coordinates the visible drop targets for the current drag.
@@ -166,17 +163,8 @@ public class DragLayout extends LinearLayout {
        boolean alreadyInSplit = mSplitScreenController != null
                && mSplitScreenController.isSplitScreenVisible();
        if (!alreadyInSplit) {
            List<ActivityManager.RunningTaskInfo> tasks = null;
            // Figure out the splashscreen info for the existing task.
            try {
                tasks = ActivityTaskManager.getService().getTasks(1,
                        false /* filterOnlyVisibleRecents */,
                        false /* keepIntentExtra */);
            } catch (RemoteException e) {
                // don't show an icon / will just use the defaults
            }
            if (tasks != null && !tasks.isEmpty()) {
                ActivityManager.RunningTaskInfo taskInfo1 = tasks.get(0);
            ActivityManager.RunningTaskInfo taskInfo1 = mPolicy.getLatestRunningTask();
            if (taskInfo1 != null) {
                Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo);
                int bgColor1 = getResizingBackgroundColor(taskInfo1);
                mDropZoneView1.setAppInfo(bgColor1, icon1);
+1 −1
Original line number Diff line number Diff line
@@ -22,7 +22,7 @@ import android.content.Context;
import android.hardware.display.DisplayManager;
import android.testing.TestableContext;

import androidx.test.InstrumentationRegistry;
import androidx.test.platform.app.InstrumentationRegistry;

import org.junit.After;
import org.junit.Before;
Loading