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
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -23,6 +23,8 @@ 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 static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
|
||||
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
|
||||
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
|
||||
@@ -32,6 +34,7 @@ import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPL
|
||||
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
|
||||
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
|
||||
|
||||
import static junit.framework.Assert.assertNull;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static junit.framework.Assert.fail;
|
||||
|
||||
@@ -50,9 +53,11 @@ import android.app.ActivityTaskManager;
|
||||
import android.app.PendingIntent;
|
||||
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.ResolveInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Insets;
|
||||
@@ -177,6 +182,12 @@ public class DragAndDropPolicyTest {
|
||||
info.configuration.windowConfiguration.setActivityType(actType);
|
||||
info.configuration.windowConfiguration.setWindowingMode(winMode);
|
||||
info.isResizeable = true;
|
||||
info.baseActivity = new ComponentName(getInstrumentation().getContext().getPackageName(),
|
||||
".ActivityWithMode" + winMode);
|
||||
ActivityInfo activityInfo = new ActivityInfo();
|
||||
activityInfo.packageName = info.baseActivity.getPackageName();
|
||||
activityInfo.name = info.baseActivity.getClassName();
|
||||
info.topActivityInfo = activityInfo;
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -252,6 +263,62 @@ public class DragAndDropPolicyTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchMultipleTask_differentActivity() {
|
||||
setRunningTask(mFullscreenAppTask);
|
||||
mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
|
||||
Intent fillInIntent = mPolicy.getStartIntentFillInIntent(mock(PendingIntent.class), 0);
|
||||
assertNull(fillInIntent);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchMultipleTask_differentActivity_inSplitscreen() {
|
||||
setRunningTask(mFullscreenAppTask);
|
||||
doReturn(true).when(mSplitScreenStarter).isSplitScreenVisible();
|
||||
doReturn(mFullscreenAppTask).when(mSplitScreenStarter).getTaskInfo(anyInt());
|
||||
mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
|
||||
Intent fillInIntent = mPolicy.getStartIntentFillInIntent(mock(PendingIntent.class), 0);
|
||||
assertNull(fillInIntent);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchMultipleTask_sameActivity() {
|
||||
setRunningTask(mFullscreenAppTask);
|
||||
|
||||
// Replace the mocked drag pending intent and ensure it resolves to the same activity
|
||||
PendingIntent launchIntent = mock(PendingIntent.class);
|
||||
ResolveInfo launchInfo = new ResolveInfo();
|
||||
launchInfo.activityInfo = mFullscreenAppTask.topActivityInfo;
|
||||
doReturn(Collections.singletonList(launchInfo))
|
||||
.when(launchIntent).queryIntentComponents(anyInt());
|
||||
mActivityClipData.getItemAt(0).getIntent().putExtra(ClipDescription.EXTRA_PENDING_INTENT,
|
||||
launchIntent);
|
||||
|
||||
mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
|
||||
Intent fillInIntent = mPolicy.getStartIntentFillInIntent(launchIntent, 0);
|
||||
assertTrue((fillInIntent.getFlags() & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchMultipleTask_sameActivity_inSplitScreen() {
|
||||
setRunningTask(mFullscreenAppTask);
|
||||
|
||||
// Replace the mocked drag pending intent and ensure it resolves to the same activity
|
||||
PendingIntent launchIntent = mock(PendingIntent.class);
|
||||
ResolveInfo launchInfo = new ResolveInfo();
|
||||
launchInfo.activityInfo = mFullscreenAppTask.topActivityInfo;
|
||||
doReturn(Collections.singletonList(launchInfo))
|
||||
.when(launchIntent).queryIntentComponents(anyInt());
|
||||
mActivityClipData.getItemAt(0).getIntent().putExtra(ClipDescription.EXTRA_PENDING_INTENT,
|
||||
launchIntent);
|
||||
|
||||
doReturn(true).when(mSplitScreenStarter).isSplitScreenVisible();
|
||||
doReturn(mFullscreenAppTask).when(mSplitScreenStarter).getTaskInfo(anyInt());
|
||||
mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
|
||||
Intent fillInIntent = mPolicy.getStartIntentFillInIntent(launchIntent, 0);
|
||||
assertTrue((fillInIntent.getFlags() & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0);
|
||||
}
|
||||
|
||||
private Target filterTargetByType(ArrayList<Target> targets, int type) {
|
||||
for (Target t : targets) {
|
||||
if (type == t.type) {
|
||||
|
||||
@@ -24,8 +24,8 @@ import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableContext;
|
||||
import android.testing.TestableLooper;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import com.android.wm.shell.common.ShellExecutor;
|
||||
|
||||
|
||||
@@ -45,8 +45,8 @@ import android.window.DisplayAreaOrganizer;
|
||||
import android.window.IWindowContainerToken;
|
||||
import android.window.WindowContainerToken;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import com.android.wm.shell.common.DisplayController;
|
||||
import com.android.wm.shell.common.DisplayLayout;
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.server.pm;
|
||||
|
||||
import static android.app.ActivityOptions.KEY_SPLASH_SCREEN_THEME;
|
||||
import static android.app.PendingIntent.FLAG_IMMUTABLE;
|
||||
import static android.app.PendingIntent.FLAG_MUTABLE;
|
||||
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
|
||||
@@ -1218,8 +1219,9 @@ public class LauncherAppsService extends SystemService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PendingIntent getActivityLaunchIntent(ComponentName component, Bundle opts,
|
||||
public PendingIntent getActivityLaunchIntent(String callingPackage, ComponentName component,
|
||||
UserHandle user) {
|
||||
ensureShortcutPermission(callingPackage);
|
||||
if (!canAccessProfile(user.getIdentifier(), "Cannot start activity")) {
|
||||
throw new ActivityNotFoundException("Activity could not be found");
|
||||
}
|
||||
@@ -1237,7 +1239,7 @@ public class LauncherAppsService extends SystemService {
|
||||
// calling identity to mirror the startActivityAsUser() call which does not validate
|
||||
// the calling user
|
||||
return PendingIntent.getActivityAsUser(mContext, 0 /* requestCode */, launchIntent,
|
||||
FLAG_IMMUTABLE, null /* options */, user);
|
||||
FLAG_MUTABLE, null /* opts */, user);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user