summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Winson Chung <winsonc@google.com> 2020-10-15 22:50:34 -0700
committer Winson Chung <winsonc@google.com> 2020-10-26 14:33:05 -0700
commit8c1fc3ea3a6d2ccfbd369eb843809eb65bed2427 (patch)
treed27714dd2a52873a75305fe9884ceb98b006d06e
parent4f2f5f15478773261be6ab6158924ea1cb2b5efd (diff)
2/ Add support for dragging shortcuts and tasks
- Add mechanism for creating PendingIntents to launch apps in the same way as starting through LauncherApps service - Require callers starting a task launch to either be the recents component or have the START_TASKS_FROM_RECENTS permission Bug: 169894807 Test: atest LauncherAppsTest Test: atest DragDropControllerTest Change-Id: I2f9f622b4ef7a8aba06b0854a1549a2c07cf38e1
-rw-r--r--core/java/android/content/ClipDescription.java19
-rw-r--r--core/java/android/content/pm/ILauncherApps.aidl3
-rw-r--r--core/java/android/content/pm/LauncherApps.java24
-rw-r--r--libs/WindowManager/Shell/res/raw/wm_shell_protolog.json6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java115
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/ClipDescriptionCompat.java39
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/LauncherAppsCompat.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java5
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java52
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java2
-rw-r--r--services/core/java/com/android/server/wm/Session.java39
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java40
12 files changed, 336 insertions, 42 deletions
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index dedfce6f6b96..38ab8aa2dcbc 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -64,12 +64,29 @@ public class ClipDescription implements Parcelable {
public static final String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";
/**
- * The MIME type for an activity.
+ * The MIME type for an activity. The ClipData must include intents with required extras
+ * {@link #EXTRA_PENDING_INTENT} and {@link Intent#EXTRA_USER}, and an optional
+ * {@link #EXTRA_ACTIVITY_OPTIONS}.
* @hide
*/
public static final String MIMETYPE_APPLICATION_ACTIVITY = "application/vnd.android.activity";
/**
+ * The MIME type for a shortcut. The ClipData must include intents with required extras
+ * {@link #EXTRA_PENDING_INTENT} and {@link Intent#EXTRA_USER}, and an optional
+ * {@link #EXTRA_ACTIVITY_OPTIONS}.
+ * @hide
+ */
+ public static final String MIMETYPE_APPLICATION_SHORTCUT = "application/vnd.android.shortcut";
+
+ /**
+ * The MIME type for a task. The ClipData must include an intent with a required extra
+ * {@link Intent#EXTRA_TASK_ID} of the task to launch.
+ * @hide
+ */
+ public static final String MIMETYPE_APPLICATION_TASK = "application/vnd.android.task";
+
+ /**
* The MIME type for data whose type is otherwise unknown.
* <p>
* Per RFC 2046, the "application" media type is to be used for discrete
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index d9ecf46069cd..d688614f6caa 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -17,6 +17,7 @@
package android.content.pm;
import android.app.IApplicationThread;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentSender;
@@ -55,6 +56,8 @@ 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,
+ in UserHandle user);
void showAppDetailsAsUser(in IApplicationThread caller, String callingPackage,
String callingFeatureId, in ComponentName component, in Rect sourceBounds,
in Bundle opts, in UserHandle user);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index b7af397cd36a..2909d66d72ff 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -17,6 +17,7 @@
package android.content.pm;
import static android.Manifest.permission;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
@@ -716,6 +717,29 @@ public class LauncherApps {
}
/**
+ * Returns a PendingIntent that would start the same activity started from
+ * {@link #startMainActivity(ComponentName, UserHandle, Rect, Bundle)}.
+ *
+ * @param component The ComponentName of the activity to launch
+ * @param startActivityOptions Options to pass to startActivity
+ * @param user The UserHandle of the profile
+ * @hide
+ */
+ @Nullable
+ public PendingIntent getMainActivityLaunchIntent(@NonNull ComponentName component,
+ @Nullable Bundle startActivityOptions, @NonNull UserHandle user) {
+ logErrorForInvalidProfileAccess(user);
+ if (DEBUG) {
+ Log.i(TAG, "GetMainActivityLaunchIntent " + component + " " + user);
+ }
+ try {
+ return mService.getActivityLaunchIntent(component, startActivityOptions, user);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns the activity info for a given intent and user handle, if it resolves. Otherwise it
* returns null.
*
diff --git a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
index 358d440686dd..f55e91e6ec40 100644
--- a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
+++ b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
@@ -145,6 +145,12 @@
"group": "WM_SHELL_DRAG_AND_DROP",
"at": "com\/android\/wm\/shell\/draganddrop\/DragLayout.java"
},
+ "1842752748": {
+ "message": "Clip description: handlingDrag=%b mimeTypes=%s",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_DRAG_AND_DROP",
+ "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
+ },
"1862198614": {
"message": "Drag event: action=%s x=%f y=%f xOffset=%f yOffset=%f",
"level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 8f8b98bbfbae..bf5b1d8a4bcc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -16,8 +16,17 @@
package com.android.wm.shell.draganddrop;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.content.ClipDescription.EXTRA_ACTIVITY_OPTIONS;
+import static android.content.ClipDescription.EXTRA_PENDING_INTENT;
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 android.content.Intent.EXTRA_PACKAGE_NAME;
+import static android.content.Intent.EXTRA_SHORTCUT_ID;
+import static android.content.Intent.EXTRA_TASK_ID;
+import static android.content.Intent.EXTRA_USER;
import static android.view.DragEvent.ACTION_DRAG_ENDED;
import static android.view.DragEvent.ACTION_DRAG_ENTERED;
import static android.view.DragEvent.ACTION_DRAG_EXITED;
@@ -33,15 +42,24 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMA
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.LauncherApps;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Slog;
import android.util.SparseArray;
import android.view.DragEvent;
@@ -68,6 +86,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
private static final String TAG = DragAndDropController.class.getSimpleName();
+ private final Context mContext;
private final DisplayController mDisplayController;
private SplitScreen mSplitScreen;
@@ -76,7 +95,8 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
private DragLayout mDragLayout;
private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
- public DragAndDropController(DisplayController displayController) {
+ public DragAndDropController(Context context, DisplayController displayController) {
+ mContext = context;
mDisplayController = displayController;
mDisplayController.addDisplayWindowListener(this);
}
@@ -135,13 +155,16 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
event.getOffsetX(), event.getOffsetY());
final int displayId = target.getDisplay().getDisplayId();
final PerDisplay pd = mDisplayDropTargets.get(displayId);
+ final ClipDescription description = event.getClipDescription();
if (event.getAction() == ACTION_DRAG_STARTED) {
- final ClipDescription description = event.getClipDescription();
- final boolean hasValidClipData = description.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY);
+ final boolean hasValidClipData = description.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY)
+ || description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT)
+ || description.hasMimeType(MIMETYPE_APPLICATION_TASK);
mIsHandlingDrag = hasValidClipData;
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Clip description: %s",
- getMimeTypes(description));
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Clip description: handlingDrag=%b mimeTypes=%s",
+ mIsHandlingDrag, getMimeTypes(description));
}
if (!mIsHandlingDrag) {
@@ -163,31 +186,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
mDragLayout.update(event);
break;
case ACTION_DROP: {
- final SurfaceControl dragSurface = event.getDragSurface();
- final View dragLayout = mDragLayout;
- final ClipData data = event.getClipData();
- return mDragLayout.drop(event, dragSurface, (dropTargetBounds) -> {
- if (dropTargetBounds != null) {
- // TODO(b/169894807): Properly handle the drop, for now just launch it
- if (data.getItemCount() > 0) {
- Intent intent = data.getItemAt(0).getIntent();
- PendingIntent pi = intent.getParcelableExtra(
- ClipDescription.EXTRA_PENDING_INTENT);
- try {
- pi.send();
- } catch (PendingIntent.CanceledException e) {
- Slog.e(TAG, "Failed to launch activity", e);
- }
- }
- }
-
- setDropTargetWindowVisibility(pd, View.INVISIBLE);
- pd.dropTarget.removeView(dragLayout);
-
- // Clean up the drag surface
- mTransaction.reparent(dragSurface, null);
- mTransaction.apply();
- });
+ return handleDrop(event, pd);
}
case ACTION_DRAG_EXITED: {
// Either one of DROP or EXITED will happen, and when EXITED we won't consume
@@ -211,6 +210,62 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
return true;
}
+ /**
+ * Handles dropping on the drop target.
+ */
+ private boolean handleDrop(DragEvent event, PerDisplay pd) {
+ final ClipData data = event.getClipData();
+ final ClipDescription description = event.getClipDescription();
+ final SurfaceControl dragSurface = event.getDragSurface();
+ final View dragLayout = mDragLayout;
+ final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK);
+ final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
+ return mDragLayout.drop(event, dragSurface, (dropTargetBounds) -> {
+ if (dropTargetBounds != null && data.getItemCount() > 0) {
+ final Intent intent = data.getItemAt(0).getIntent();
+ // TODO(b/169894807): Properly handle the drop, for now just launch it
+ if (isTask) {
+ int taskId = intent.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
+ try {
+ ActivityTaskManager.getService().startActivityFromRecents(
+ taskId, null);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to launch task", e);
+ }
+ } else if (isShortcut) {
+ try {
+ Bundle opts = intent.hasExtra(EXTRA_ACTIVITY_OPTIONS)
+ ? intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS)
+ : null;
+ LauncherApps launcherApps =
+ mContext.getSystemService(LauncherApps.class);
+ launcherApps.startShortcut(
+ intent.getStringExtra(EXTRA_PACKAGE_NAME),
+ intent.getStringExtra(EXTRA_SHORTCUT_ID),
+ null /* sourceBounds */, opts,
+ intent.getParcelableExtra(EXTRA_USER));
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "Failed to launch shortcut", e);
+ }
+ } else {
+ PendingIntent pi = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
+ try {
+ pi.send();
+ } catch (PendingIntent.CanceledException e) {
+ Slog.e(TAG, "Failed to launch activity", e);
+ }
+ }
+ }
+
+ setDropTargetWindowVisibility(pd, View.INVISIBLE);
+ pd.dropTarget.removeView(dragLayout);
+
+ // Clean up the drag surface
+ mTransaction.reparent(dragSurface, null);
+ mTransaction.apply();
+ });
+ }
+
private void setDropTargetWindowVisibility(PerDisplay pd, int visibility) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
"Set drop target window visibility: displayId=%d visibility=%d",
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ClipDescriptionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ClipDescriptionCompat.java
new file mode 100644
index 000000000000..0b1141ee9b30
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ClipDescriptionCompat.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.shared.system;
+
+import android.content.ClipDescription;
+import android.content.Intent;
+
+/**
+ * Wrapper around ClipDescription.
+ */
+public abstract class ClipDescriptionCompat {
+
+ public static String MIMETYPE_APPLICATION_ACTIVITY =
+ ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
+
+ public static String MIMETYPE_APPLICATION_SHORTCUT =
+ ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
+
+ public static String MIMETYPE_APPLICATION_TASK =
+ ClipDescription.MIMETYPE_APPLICATION_TASK;
+
+ public static String EXTRA_PENDING_INTENT = ClipDescription.EXTRA_PENDING_INTENT;
+
+ public static String EXTRA_TASK_ID = Intent.EXTRA_TASK_ID;
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/LauncherAppsCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/LauncherAppsCompat.java
new file mode 100644
index 000000000000..d24c779b1416
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/LauncherAppsCompat.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.shared.system;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.pm.LauncherApps;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+/**
+ * Wrapper around LauncherApps.
+ */
+public abstract class LauncherAppsCompat {
+
+ public static PendingIntent getMainActivityLaunchIntent(LauncherApps launcherApps,
+ ComponentName component, Bundle startActivityOptions, UserHandle user) {
+ return launcherApps.getMainActivityLaunchIntent(component, startActivityOptions, user);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index ead2fc1cd9dc..cff1c9d086ea 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -102,8 +102,9 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static DragAndDropController provideDragAndDropController(DisplayController displayController) {
- return new DragAndDropController(displayController);
+ static DragAndDropController provideDragAndDropController(Context context,
+ DisplayController displayController) {
+ return new DragAndDropController(context, displayController);
}
@WMSingleton
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 8eebf2a8d9d9..b679c0fbab83 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.pm.LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS;
@@ -32,6 +33,7 @@ import android.app.IApplicationThread;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.app.usage.UsageStatsManagerInternal;
+import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -1018,6 +1020,32 @@ public class LauncherAppsService extends SystemService {
}
@Override
+ public PendingIntent getActivityLaunchIntent(ComponentName component, Bundle opts,
+ UserHandle user) {
+ if (!canAccessProfile(user.getIdentifier(), "Cannot start activity")) {
+ throw new ActivityNotFoundException("Activity could not be found");
+ }
+
+ final Intent launchIntent = getMainActivityLaunchIntent(component, user);
+ if (launchIntent == null) {
+ throw new SecurityException("Attempt to launch activity without "
+ + " category Intent.CATEGORY_LAUNCHER " + component);
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ // If we reach here, we've verified that the caller has access to the profile and
+ // is launching an exported activity with CATEGORY_LAUNCHER so we can clear the
+ // calling identity to mirror the startActivityAsUser() call which does not validate
+ // the calling user
+ return PendingIntent.getActivityAsUser(mContext, 0 /* requestCode */, launchIntent,
+ FLAG_IMMUTABLE, opts, user);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public void startActivityAsUser(IApplicationThread caller, String callingPackage,
String callingFeatureId, ComponentName component, Rect sourceBounds,
Bundle opts, UserHandle user) throws RemoteException {
@@ -1025,9 +1053,24 @@ public class LauncherAppsService extends SystemService {
return;
}
+ Intent launchIntent = getMainActivityLaunchIntent(component, user);
+ if (launchIntent == null) {
+ throw new SecurityException("Attempt to launch activity without "
+ + " category Intent.CATEGORY_LAUNCHER " + component);
+ }
+ launchIntent.setSourceBounds(sourceBounds);
+
+ mActivityTaskManagerInternal.startActivityAsUser(caller, callingPackage,
+ callingFeatureId, launchIntent, /* resultTo= */ null,
+ Intent.FLAG_ACTIVITY_NEW_TASK, opts, user.getIdentifier());
+ }
+
+ /**
+ * Returns the main activity launch intent for the given component package.
+ */
+ private Intent getMainActivityLaunchIntent(ComponentName component, UserHandle user) {
Intent launchIntent = new Intent(Intent.ACTION_MAIN);
launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- launchIntent.setSourceBounds(sourceBounds);
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
launchIntent.setPackage(component.getPackageName());
@@ -1066,15 +1109,12 @@ public class LauncherAppsService extends SystemService {
}
}
if (!canLaunch) {
- throw new SecurityException("Attempt to launch activity without "
- + " category Intent.CATEGORY_LAUNCHER " + component);
+ return null;
}
} finally {
Binder.restoreCallingIdentity(ident);
}
- mActivityTaskManagerInternal.startActivityAsUser(caller, callingPackage,
- callingFeatureId, launchIntent, /* resultTo= */ null,
- Intent.FLAG_ACTIVITY_NEW_TASK, opts, user.getIdentifier());
+ return launchIntent;
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 8e5add916620..0fac8358c9c4 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3606,7 +3606,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
/** This can be called with or without the global lock held. */
- private void enforceCallerIsRecentsOrHasPermission(String permission, String func) {
+ void enforceCallerIsRecentsOrHasPermission(String permission, String func) {
if (!getRecentTasks().isCallerRecents(Binder.getCallingUid())) {
mAmInternal.enforceCallingPermission(permission, func);
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 72edb86283d2..6fbd35164874 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -20,7 +20,12 @@ import static android.Manifest.permission.DEVICE_POWER;
import static android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
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 android.content.Intent.EXTRA_SHORTCUT_ID;
+import static android.content.Intent.EXTRA_TASK_ID;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -49,6 +54,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.MergedConfiguration;
import android.util.Slog;
@@ -297,18 +303,25 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
*/
@VisibleForTesting
public void validateAndResolveDragMimeTypeExtras(ClipData data, int callingUid) {
+ if (Binder.getCallingUid() == Process.SYSTEM_UID) {
+ throw new IllegalStateException("Need to validate before calling identify is cleared");
+ }
final ClipDescription desc = data != null ? data.getDescription() : null;
if (desc == null) {
return;
}
// Ensure that only one of the app mime types are set
final boolean hasActivity = desc.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY);
- int appMimeTypeCount = (hasActivity ? 1 : 0);
+ final boolean hasShortcut = desc.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
+ final boolean hasTask = desc.hasMimeType(MIMETYPE_APPLICATION_TASK);
+ int appMimeTypeCount = (hasActivity ? 1 : 0)
+ + (hasShortcut ? 1 : 0)
+ + (hasTask ? 1 : 0);
if (appMimeTypeCount == 0) {
return;
} else if (appMimeTypeCount > 1) {
throw new IllegalArgumentException("Can not specify more than one of activity, "
- + "or task mime types");
+ + "shortcut, or task mime types");
}
// Ensure that data is provided and that they are intents
if (data.getItemCount() == 0) {
@@ -344,6 +357,28 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
} finally {
Binder.restoreCallingIdentity(origId);
}
+ } else if (hasShortcut) {
+ mService.mAtmService.enforceCallerIsRecentsOrHasPermission(START_TASKS_FROM_RECENTS,
+ "performDrag");
+ for (int i = 0; i < data.getItemCount(); i++) {
+ final Intent intent = data.getItemAt(i).getIntent();
+ final UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
+ if (!intent.hasExtra(EXTRA_SHORTCUT_ID)
+ || TextUtils.isEmpty(intent.getStringExtra(EXTRA_SHORTCUT_ID))
+ || user == null) {
+ throw new IllegalArgumentException("Clip item must include the shortcut id and "
+ + "the user to launch for.");
+ }
+ }
+ } else if (hasTask) {
+ mService.mAtmService.enforceCallerIsRecentsOrHasPermission(START_TASKS_FROM_RECENTS,
+ "performDrag");
+ for (int i = 0; i < data.getItemCount(); i++) {
+ final Intent intent = data.getItemAt(i).getIntent();
+ if (intent.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID) == INVALID_TASK_ID) {
+ throw new IllegalArgumentException("Clip item must include the task id.");
+ }
+ }
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 64a05bb361e9..747fb8999276 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -19,6 +19,8 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
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 android.view.DragEvent.ACTION_DRAG_STARTED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -82,6 +84,8 @@ import java.util.concurrent.TimeUnit;
@RunWith(WindowTestRunner.class)
public class DragDropControllerTests extends WindowTestsBase {
private static final int TIMEOUT_MS = 3000;
+ private static final int TEST_UID = 12345;
+
private TestDragDropController mTarget;
private WindowState mWindow;
private IBinder mToken;
@@ -278,6 +282,42 @@ public class DragDropControllerTests extends WindowTestsBase {
return clipData;
}
+ @Test
+ public void testValidateAppShortcutArguments() {
+ final Session session = new Session(mWm, new IWindowSessionCallback.Stub() {
+ @Override
+ public void onAnimatorScaleChanged(float scale) {}
+ });
+ try {
+ final ClipData clipData = new ClipData(
+ new ClipDescription("drag", new String[] { MIMETYPE_APPLICATION_SHORTCUT }),
+ new ClipData.Item(new Intent()));
+
+ session.validateAndResolveDragMimeTypeExtras(clipData, TEST_UID);
+ fail("Expected failure without shortcut id");
+ } catch (IllegalArgumentException e) {
+ // Expected failure
+ }
+ }
+
+ @Test
+ public void testValidateAppTaskArguments() {
+ final Session session = new Session(mWm, new IWindowSessionCallback.Stub() {
+ @Override
+ public void onAnimatorScaleChanged(float scale) {}
+ });
+ try {
+ final ClipData clipData = new ClipData(
+ new ClipDescription("drag", new String[] { MIMETYPE_APPLICATION_TASK }),
+ new ClipData.Item(new Intent()));
+
+ session.validateAndResolveDragMimeTypeExtras(clipData, TEST_UID);
+ fail("Expected failure without task id");
+ } catch (IllegalArgumentException e) {
+ // Expected failure
+ }
+ }
+
private void doDragAndDrop(int flags, ClipData data, float dropX, float dropY) {
startDrag(flags, data, () -> {
mTarget.handleMotionEvent(false, dropX, dropY);