Add support for launching a shortcut in WindowContainerTransaction

Some shell actions require the ability to launch a shortcut either
in a transaction and/or with other window operations (eg. Bubbles
and Split-screen).

This required changes to LauncherAppsService to support forwarding
the caller information internally from systemserver.

Bug: 209073176
Test: existing LauncherAppsService tests still pass
Change-Id: I11893abca24cab0b899c3385af0ca46932bcc9e3
This commit is contained in:
Evan Rosky
2021-12-07 20:50:14 -08:00
parent 7dbd479b36
commit 95cd24e26e
4 changed files with 139 additions and 17 deletions

View File

@@ -23,6 +23,7 @@ import android.app.PendingIntent;
import android.app.WindowConfiguration;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ShortcutInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
@@ -434,6 +435,21 @@ public final class WindowContainerTransaction implements Parcelable {
return this;
}
/**
* Starts activity(s) from a shortcut.
* @param callingPackage The package launching the shortcut.
* @param shortcutInfo Information about the shortcut to start
* @param options bundle containing ActivityOptions for the task's top activity.
* @hide
*/
@NonNull
public WindowContainerTransaction startShortcut(@NonNull String callingPackage,
@NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options) {
mHierarchyOps.add(HierarchyOp.createForStartShortcut(
callingPackage, shortcutInfo, options));
return this;
}
/**
* Creates a new TaskFragment with the given options.
* @param taskFragmentOptions the options used to create the TaskFragment.
@@ -957,11 +973,16 @@ public final class WindowContainerTransaction implements Parcelable {
public static final int HIERARCHY_OP_TYPE_REPARENT_CHILDREN = 11;
public static final int HIERARCHY_OP_TYPE_PENDING_INTENT = 12;
public static final int HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS = 13;
public static final int HIERARCHY_OP_TYPE_START_SHORTCUT = 14;
// The following key(s) are for use with mLaunchOptions:
// When launching a task (eg. from recents), this is the taskId to be launched.
public static final String LAUNCH_KEY_TASK_ID = "android:transaction.hop.taskId";
// When starting from a shortcut, this contains the calling package.
public static final String LAUNCH_KEY_SHORTCUT_CALLING_PACKAGE =
"android:transaction.hop.shortcut_calling_package";
private final int mType;
// Container we are performing the operation on.
@@ -999,6 +1020,9 @@ public final class WindowContainerTransaction implements Parcelable {
@Nullable
private PendingIntent mPendingIntent;
@Nullable
private ShortcutInfo mShortcutInfo;
public static HierarchyOp createForReparent(
@NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT)
@@ -1058,6 +1082,17 @@ public final class WindowContainerTransaction implements Parcelable {
.build();
}
/** Create a hierarchy op for starting a shortcut. */
public static HierarchyOp createForStartShortcut(@NonNull String callingPackage,
@NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options) {
final Bundle fullOptions = options == null ? new Bundle() : options;
fullOptions.putString(LAUNCH_KEY_SHORTCUT_CALLING_PACKAGE, callingPackage);
return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_START_SHORTCUT)
.setShortcutInfo(shortcutInfo)
.setLaunchOptions(fullOptions)
.build();
}
/** Create a hierarchy op for setting launch adjacent flag root. */
public static HierarchyOp createForSetLaunchAdjacentFlagRoot(IBinder container,
boolean clearRoot) {
@@ -1085,6 +1120,7 @@ public final class WindowContainerTransaction implements Parcelable {
mActivityIntent = copy.mActivityIntent;
mTaskFragmentCreationOptions = copy.mTaskFragmentCreationOptions;
mPendingIntent = copy.mPendingIntent;
mShortcutInfo = copy.mShortcutInfo;
}
protected HierarchyOp(Parcel in) {
@@ -1100,6 +1136,7 @@ public final class WindowContainerTransaction implements Parcelable {
mActivityIntent = in.readTypedObject(Intent.CREATOR);
mTaskFragmentCreationOptions = in.readTypedObject(TaskFragmentCreationParams.CREATOR);
mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
mShortcutInfo = in.readTypedObject(ShortcutInfo.CREATOR);
}
public int getType() {
@@ -1170,6 +1207,11 @@ public final class WindowContainerTransaction implements Parcelable {
return mPendingIntent;
}
@Nullable
public ShortcutInfo getShortcutInfo() {
return mShortcutInfo;
}
@Override
public String toString() {
switch (mType) {
@@ -1212,6 +1254,9 @@ public final class WindowContainerTransaction implements Parcelable {
case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS:
return "{SetAdjacentTaskFragments: container=" + mContainer
+ " adjacentContainer=" + mReparent + "}";
case HIERARCHY_OP_TYPE_START_SHORTCUT:
return "{StartShortcut: options=" + mLaunchOptions + " info=" + mShortcutInfo
+ "}";
default:
return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
+ " mToTop=" + mToTop
@@ -1234,6 +1279,7 @@ public final class WindowContainerTransaction implements Parcelable {
dest.writeTypedObject(mActivityIntent, flags);
dest.writeTypedObject(mTaskFragmentCreationOptions, flags);
dest.writeTypedObject(mPendingIntent, flags);
dest.writeTypedObject(mShortcutInfo, flags);
}
@Override
@@ -1287,6 +1333,9 @@ public final class WindowContainerTransaction implements Parcelable {
@Nullable
private PendingIntent mPendingIntent;
@Nullable
private ShortcutInfo mShortcutInfo;
Builder(int type) {
mType = type;
}
@@ -1347,6 +1396,11 @@ public final class WindowContainerTransaction implements Parcelable {
return this;
}
Builder setShortcutInfo(@Nullable ShortcutInfo shortcutInfo) {
mShortcutInfo = shortcutInfo;
return this;
}
HierarchyOp build() {
final HierarchyOp hierarchyOp = new HierarchyOp(mType);
hierarchyOp.mContainer = mContainer;
@@ -1364,6 +1418,7 @@ public final class WindowContainerTransaction implements Parcelable {
hierarchyOp.mActivityIntent = mActivityIntent;
hierarchyOp.mPendingIntent = mPendingIntent;
hierarchyOp.mTaskFragmentCreationOptions = mTaskFragmentCreationOptions;
hierarchyOp.mShortcutInfo = mShortcutInfo;
return hierarchyOp;
}

View File

@@ -121,6 +121,7 @@ public class LauncherAppsService extends SystemService {
public void onStart() {
publishBinderService(Context.LAUNCHER_APPS_SERVICE, mLauncherAppsImpl);
mLauncherAppsImpl.registerLoadingProgressForIncrementalApps();
LocalServices.addService(LauncherAppsServiceInternal.class, mLauncherAppsImpl.mInternal);
}
static class BroadcastCookie {
@@ -137,6 +138,17 @@ public class LauncherAppsService extends SystemService {
}
}
/**
* Local system service interface.
* @hide Only for use within system server
*/
public abstract static class LauncherAppsServiceInternal {
/** Same as startShortcut except supports forwarding of caller uid/pid. */
public abstract boolean startShortcut(int callerUid, int callerPid, String callingPackage,
String packageName, String featureId, String shortcutId, Rect sourceBounds,
Bundle startActivityOptions, int targetUserId);
}
@VisibleForTesting
static class LauncherAppsImpl extends ILauncherApps.Stub {
private static final boolean DEBUG = false;
@@ -168,6 +180,8 @@ public class LauncherAppsService extends SystemService {
private PackageInstallerService mPackageInstallerService;
final LauncherAppsServiceInternal mInternal;
public LauncherAppsImpl(Context context) {
mContext = context;
mIPM = AppGlobals.getPackageManager();
@@ -189,6 +203,7 @@ public class LauncherAppsService extends SystemService {
mShortcutServiceInternal.addShortcutChangeCallback(mShortcutChangeHandler);
mCallbackHandler = BackgroundThread.getHandler();
mDpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
mInternal = new LocalService();
}
@VisibleForTesting
@@ -359,11 +374,15 @@ public class LauncherAppsService extends SystemService {
* group.
*/
private boolean canAccessProfile(int targetUserId, String message) {
final int callingUserId = injectCallingUserId();
return canAccessProfile(injectBinderCallingUid(), injectCallingUserId(),
injectBinderCallingPid(), targetUserId, message);
}
private boolean canAccessProfile(int callingUid, int callingUserId, int callingPid,
int targetUserId, String message) {
if (targetUserId == callingUserId) return true;
if (injectHasInteractAcrossUsersFullPermission(injectBinderCallingPid(),
injectBinderCallingUid())) {
if (injectHasInteractAcrossUsersFullPermission(callingPid, callingUid)) {
return true;
}
@@ -379,25 +398,29 @@ public class LauncherAppsService extends SystemService {
injectRestoreCallingIdentity(ident);
}
return mUserManagerInternal.isProfileAccessible(injectCallingUserId(), targetUserId,
return mUserManagerInternal.isProfileAccessible(callingUserId, targetUserId,
message, true);
}
private void verifyCallingPackage(String callingPackage) {
verifyCallingPackage(callingPackage, injectBinderCallingUid());
}
@VisibleForTesting // We override it in unit tests
void verifyCallingPackage(String callingPackage) {
void verifyCallingPackage(String callingPackage, int callerUid) {
int packageUid = -1;
try {
packageUid = mIPM.getPackageUid(callingPackage,
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_UNINSTALLED_PACKAGES,
UserHandle.getUserId(getCallingUid()));
UserHandle.getUserId(callerUid));
} catch (RemoteException ignore) {
}
if (packageUid < 0) {
Log.e(TAG, "Package not found: " + callingPackage);
}
if (packageUid != injectBinderCallingUid()) {
if (packageUid != callerUid) {
throw new SecurityException("Calling package name mismatch");
}
}
@@ -797,9 +820,15 @@ public class LauncherAppsService extends SystemService {
}
private void ensureShortcutPermission(@NonNull String callingPackage) {
verifyCallingPackage(callingPackage);
if (!mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
callingPackage, injectBinderCallingPid(), injectBinderCallingUid())) {
ensureShortcutPermission(injectBinderCallingUid(), injectBinderCallingPid(),
callingPackage);
}
private void ensureShortcutPermission(int callerUid, int callerPid,
@NonNull String callingPackage) {
verifyCallingPackage(callingPackage, callerUid);
if (!mShortcutServiceInternal.hasShortcutHostPermission(UserHandle.getUserId(callerUid),
callingPackage, callerPid, callerUid)) {
throw new SecurityException("Caller can't access shortcut information");
}
}
@@ -989,20 +1018,28 @@ public class LauncherAppsService extends SystemService {
public boolean startShortcut(String callingPackage, String packageName, String featureId,
String shortcutId, Rect sourceBounds, Bundle startActivityOptions,
int targetUserId) {
verifyCallingPackage(callingPackage);
return startShortcutInner(injectBinderCallingUid(), injectBinderCallingPid(),
injectCallingUserId(), callingPackage, packageName, featureId, shortcutId,
sourceBounds, startActivityOptions, targetUserId);
}
private boolean startShortcutInner(int callerUid, int callerPid, int callingUserId,
String callingPackage, String packageName, String featureId, String shortcutId,
Rect sourceBounds, Bundle startActivityOptions, int targetUserId) {
verifyCallingPackage(callingPackage, callerUid);
if (!canAccessProfile(targetUserId, "Cannot start activity")) {
return false;
}
// Even without the permission, pinned shortcuts are always launchable.
if (!mShortcutServiceInternal.isPinnedByCaller(getCallingUserId(),
if (!mShortcutServiceInternal.isPinnedByCaller(callingUserId,
callingPackage, packageName, shortcutId, targetUserId)) {
ensureShortcutPermission(callingPackage);
ensureShortcutPermission(callerUid, callerPid, callingPackage);
}
final Intent[] intents = mShortcutServiceInternal.createShortcutIntents(
getCallingUserId(), callingPackage, packageName, shortcutId, targetUserId,
injectBinderCallingPid(), injectBinderCallingUid());
callingUserId, callingPackage, packageName, shortcutId, targetUserId,
callerPid, callerUid);
if (intents == null || intents.length == 0) {
return false;
}
@@ -1023,7 +1060,7 @@ public class LauncherAppsService extends SystemService {
// Replace theme for splash screen
final String splashScreenThemeResName =
mShortcutServiceInternal.getShortcutStartingThemeResName(getCallingUserId(),
mShortcutServiceInternal.getShortcutStartingThemeResName(callingUserId,
callingPackage, packageName, shortcutId, targetUserId);
if (splashScreenThemeResName != null && !splashScreenThemeResName.isEmpty()) {
if (startActivityOptions == null) {
@@ -1809,5 +1846,16 @@ public class LauncherAppsService extends SystemService {
}
}
}
final class LocalService extends LauncherAppsServiceInternal {
@Override
public boolean startShortcut(int callerUid, int callerPid, String callingPackage,
String packageName, String featureId, String shortcutId, Rect sourceBounds,
Bundle startActivityOptions, int targetUserId) {
return LauncherAppsImpl.this.startShortcutInner(callerUid, callerPid,
UserHandle.getUserId(callerUid), callingPackage, packageName, featureId,
shortcutId, sourceBounds, startActivityOptions, targetUserId);
}
}
}
}

View File

@@ -33,6 +33,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_SHORTCUT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
@@ -79,6 +80,8 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.function.pooled.PooledConsumer;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.pm.LauncherAppsService.LauncherAppsServiceInternal;
import java.util.ArrayList;
import java.util.HashMap;
@@ -785,6 +788,22 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
null /* requiredPermission */, options);
break;
}
case HIERARCHY_OP_TYPE_START_SHORTCUT: {
final Bundle launchOpts = hop.getLaunchOptions();
final String callingPackage = launchOpts.getString(
WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_SHORTCUT_CALLING_PACKAGE);
launchOpts.remove(
WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_SHORTCUT_CALLING_PACKAGE);
final LauncherAppsServiceInternal launcherApps = LocalServices.getService(
LauncherAppsServiceInternal.class);
launcherApps.startShortcut(caller.mUid, caller.mPid, callingPackage,
hop.getShortcutInfo().getPackage(), null /* default featureId */,
hop.getShortcutInfo().getId(), null /* sourceBounds */, launchOpts,
hop.getShortcutInfo().getUserId());
break;
}
case HIERARCHY_OP_TYPE_REPARENT_CHILDREN: {
final WindowContainer oldParent = WindowContainer.fromBinder(hop.getContainer());
final WindowContainer newParent = hop.getNewParent() != null

View File

@@ -579,7 +579,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
}
@Override
public void verifyCallingPackage(String callingPackage) {
public void verifyCallingPackage(String callingPackage, int callerUid) {
// SKIP
}