Merge "Support multiple intents in ShortcutInfo" into nyc-mr1-dev
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index fc3596e..83e2678 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -165,10 +165,10 @@
int userId);
/**
- * Start an activity {@code intent} as if {@code packageName} on user {@code userId} did it.
+ * Start activity {@code intents} as if {@code packageName} on user {@code userId} did it.
*
* @return error codes used by {@link IActivityManager#startActivity} and its siblings.
*/
- public abstract int startActivityAsPackage(String packageName,
- int userId, Intent intent, Bundle bOptions);
+ public abstract int startActivitiesAsPackage(String packageName,
+ int userId, Intent[] intents, Bundle bOptions);
}
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 54f1b76..0ec1623 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -182,16 +182,16 @@
private ArraySet<String> mCategories;
/**
- * Intent *with extras removed*.
+ * Intents *with extras removed*.
*/
@Nullable
- private Intent mIntent;
+ private Intent[] mIntents;
/**
- * Extras for the intent.
+ * Extras for the intents.
*/
@Nullable
- private PersistableBundle mIntentPersistableExtras;
+ private PersistableBundle[] mIntentPersistableExtrases;
private int mRank;
@@ -241,20 +241,36 @@
mDisabledMessage = b.mDisabledMessage;
mDisabledMessageResId = b.mDisabledMessageResId;
mCategories = cloneCategories(b.mCategories);
- mIntent = b.mIntent;
- if (mIntent != null) {
- final Bundle intentExtras = mIntent.getExtras();
- if (intentExtras != null) {
- mIntent.replaceExtras((Bundle) null);
- mIntentPersistableExtras = new PersistableBundle(intentExtras);
- }
- }
+ mIntents = cloneIntents(b.mIntents);
+ fixUpIntentExtras();
mRank = b.mRank;
mExtras = b.mExtras;
updateTimestamp();
}
- private ArraySet<String> cloneCategories(Set<String> source) {
+ /**
+ * Extract extras from {@link #mIntents} and set them to {@link #mIntentPersistableExtrases}
+ * as {@link PersistableBundle}, and remove extras from the original intents.
+ */
+ private void fixUpIntentExtras() {
+ if (mIntents == null) {
+ mIntentPersistableExtrases = null;
+ return;
+ }
+ mIntentPersistableExtrases = new PersistableBundle[mIntents.length];
+ for (int i = 0; i < mIntents.length; i++) {
+ final Intent intent = mIntents[i];
+ final Bundle extras = intent.getExtras();
+ if (extras == null) {
+ mIntentPersistableExtrases[i] = null;
+ } else {
+ mIntentPersistableExtrases[i] = new PersistableBundle(extras);
+ intent.replaceExtras((Bundle) null);
+ }
+ }
+ }
+
+ private static ArraySet<String> cloneCategories(Set<String> source) {
if (source == null) {
return null;
}
@@ -267,6 +283,32 @@
return ret;
}
+ private static Intent[] cloneIntents(Intent[] intents) {
+ if (intents == null) {
+ return null;
+ }
+ final Intent[] ret = new Intent[intents.length];
+ for (int i = 0; i < ret.length; i++) {
+ if (intents[i] != null) {
+ ret[i] = new Intent(intents[i]);
+ }
+ }
+ return ret;
+ }
+
+ private static PersistableBundle[] clonePersistableBundle(PersistableBundle[] bundle) {
+ if (bundle == null) {
+ return null;
+ }
+ final PersistableBundle[] ret = new PersistableBundle[bundle.length];
+ for (int i = 0; i < ret.length; i++) {
+ if (bundle[i] != null) {
+ ret[i] = new PersistableBundle(bundle[i]);
+ }
+ }
+ return ret;
+ }
+
/**
* Throws if any of the mandatory fields is not set.
*
@@ -278,7 +320,8 @@
if (mTitle == null && mTitleResId == 0) {
throw new IllegalArgumentException("Short label must be provided");
}
- Preconditions.checkNotNull(mIntent, "Shortcut Intent must be provided");
+ Preconditions.checkNotNull(mIntents, "Shortcut Intent must be provided");
+ Preconditions.checkArgument(mIntents.length > 0, "Shortcut Intent must be provided");
}
/**
@@ -310,8 +353,9 @@
mDisabledMessageResId = source.mDisabledMessageResId;
mCategories = cloneCategories(source.mCategories);
if ((cloneFlags & CLONE_REMOVE_INTENT) == 0) {
- mIntent = source.mIntent;
- mIntentPersistableExtras = source.mIntentPersistableExtras;
+ mIntents = cloneIntents(source.mIntents);
+ mIntentPersistableExtrases =
+ clonePersistableBundle(source.mIntentPersistableExtrases);
}
mRank = source.mRank;
mExtras = source.mExtras;
@@ -620,9 +664,10 @@
if (source.mCategories != null) {
mCategories = cloneCategories(source.mCategories);
}
- if (source.mIntent != null) {
- mIntent = source.mIntent;
- mIntentPersistableExtras = source.mIntentPersistableExtras;
+ if (source.mIntents != null) {
+ mIntents = cloneIntents(source.mIntents);
+ mIntentPersistableExtrases =
+ clonePersistableBundle(source.mIntentPersistableExtrases);
}
if (source.mRank != RANK_NOT_SET) {
mRank = source.mRank;
@@ -684,7 +729,7 @@
private Set<String> mCategories;
- private Intent mIntent;
+ private Intent[] mIntents;
private int mRank = RANK_NOT_SET;
@@ -912,12 +957,11 @@
* supported so the system can persist them.
*
* @see ShortcutInfo#getIntent()
+ * @see #setIntents(Intent[])
*/
@NonNull
public Builder setIntent(@NonNull Intent intent) {
- mIntent = Preconditions.checkNotNull(intent, "intent cannot be null");
- Preconditions.checkNotNull(mIntent.getAction(), "intent's action must be set");
- return this;
+ return setIntents(new Intent[]{intent});
}
/**
@@ -930,7 +974,15 @@
*/
@NonNull
public Builder setIntents(@NonNull Intent[] intents) {
- throw new RuntimeException("NOT SUPPORTED YET");
+ Preconditions.checkNotNull(intents, "intents cannot be null");
+ Preconditions.checkNotNull(intents.length, "intents cannot be empty");
+ for (Intent intent : intents) {
+ Preconditions.checkNotNull(intent, "intents cannot contain null");
+ Preconditions.checkNotNull(intent.getAction(), "intent's action must be set");
+ }
+ // Make sure always clone incoming intents.
+ mIntents = cloneIntents(intents);
+ return this;
}
/**
@@ -1098,7 +1150,7 @@
}
/**
- * Return the intent. (Or the last intent set with {@link Builder#setIntents(Intent[])}.
+ * Return the intent. If setIntents() was used, then return the last intent in the array.
*
* <p>Launcher applications <b>cannot</b> see the intent. If a {@link ShortcutInfo} is
* obtained via {@link LauncherApps}, then this method will always return null.
@@ -1108,13 +1160,12 @@
*/
@Nullable
public Intent getIntent() {
- if (mIntent == null) {
+ if (mIntents == null || mIntents.length == 0) {
return null;
}
- final Intent intent = new Intent(mIntent);
- intent.replaceExtras(
- mIntentPersistableExtras != null ? new Bundle(mIntentPersistableExtras) : null);
- return intent;
+ final int last = mIntents.length - 1;
+ final Intent intent = new Intent(mIntents[last]);
+ return setIntentExtras(intent, mIntentPersistableExtrases[last]);
}
/**
@@ -1127,27 +1178,34 @@
* @see Builder#setIntents(Intent[])
*/
@Nullable
- public Intent getIntents() {
- throw new RuntimeException("NOT SUPPORTED YET");
+ public Intent[] getIntents() {
+ final Intent[] ret = new Intent[mIntents.length];
+
+ for (int i = 0; i < ret.length; i++) {
+ ret[i] = new Intent(mIntents[i]);
+ setIntentExtras(ret[i], mIntentPersistableExtrases[i]);
+ }
+
+ return ret;
}
/**
- * Return "raw" intent, which is the original intent without the extras.
+ * Return "raw" intents, which is the original intents without the extras.
* @hide
*/
@Nullable
- public Intent getIntentNoExtras() {
- return mIntent;
+ public Intent[] getIntentsNoExtras() {
+ return mIntents;
}
/**
- * The extras in the intent. We convert extras into {@link PersistableBundle} so we can
+ * The extras in the intents. We convert extras into {@link PersistableBundle} so we can
* persist them.
* @hide
*/
@Nullable
- public PersistableBundle getIntentPersistableExtras() {
- return mIntentPersistableExtras;
+ public PersistableBundle[] getIntentPersistableExtrases() {
+ return mIntentPersistableExtrases;
}
/**
@@ -1500,19 +1558,22 @@
*
* @hide
*/
- public void setIntent(Intent intent) throws IllegalArgumentException {
- Preconditions.checkNotNull(intent);
+ public void setIntents(Intent[] intents) throws IllegalArgumentException {
+ Preconditions.checkNotNull(intents);
+ Preconditions.checkArgument(intents.length > 0);
- final Bundle intentExtras = intent.getExtras();
+ mIntents = cloneIntents(intents);
+ fixUpIntentExtras();
+ }
- mIntent = intent;
-
- if (intentExtras != null) {
+ /** @hide */
+ public static Intent setIntentExtras(Intent intent, PersistableBundle extras) {
+ if (extras == null) {
intent.replaceExtras((Bundle) null);
- mIntentPersistableExtras = new PersistableBundle(intentExtras);
} else {
- mIntentPersistableExtras = null;
+ intent.replaceExtras(new Bundle(extras));
}
+ return intent;
}
/**
@@ -1546,8 +1607,8 @@
mTextResId = source.readInt();
mDisabledMessage = source.readCharSequence();
mDisabledMessageResId = source.readInt();
- mIntent = source.readParcelable(cl);
- mIntentPersistableExtras = source.readParcelable(cl);
+ mIntents = source.readParcelableArray(cl, Intent.class);
+ mIntentPersistableExtrases = source.readParcelableArray(cl, PersistableBundle.class);
mRank = source.readInt();
mExtras = source.readParcelable(cl);
mBitmapPath = source.readString();
@@ -1592,8 +1653,8 @@
dest.writeCharSequence(mDisabledMessage);
dest.writeInt(mDisabledMessageResId);
- dest.writeParcelable(mIntent, flags);
- dest.writeParcelable(mIntentPersistableExtras, flags);
+ dest.writeParcelableArray(mIntents, flags);
+ dest.writeParcelableArray(mIntentPersistableExtrases, flags);
dest.writeInt(mRank);
dest.writeParcelable(mExtras, flags);
dest.writeString(mBitmapPath);
@@ -1723,11 +1784,27 @@
sb.append(", timestamp=");
sb.append(mLastChangedTimestamp);
- sb.append(", intent=");
- sb.append(mIntent);
-
- sb.append(", intentExtras=");
- sb.append(secure ? "***" : mIntentPersistableExtras);
+ sb.append(", intents=");
+ if (mIntents == null) {
+ sb.append("null");
+ } else {
+ if (secure) {
+ sb.append("size:");
+ sb.append(mIntents.length);
+ } else {
+ final int size = mIntents.length;
+ sb.append("[");
+ String sep = "";
+ for (int i = 0; i < size; i++) {
+ sb.append(sep);
+ sep = ", ";
+ sb.append(mIntents[i]);
+ sb.append("/");
+ sb.append(mIntentPersistableExtrases[i]);
+ }
+ sb.append("]");
+ }
+ }
sb.append(", extras=");
sb.append(mExtras);
@@ -1754,9 +1831,8 @@
Icon icon, CharSequence title, int titleResId, String titleResName,
CharSequence text, int textResId, String textResName,
CharSequence disabledMessage, int disabledMessageResId, String disabledMessageResName,
- Set<String> categories,
- Intent intent, PersistableBundle intentPersistableExtras,
- int rank, PersistableBundle extras, long lastChangedTimestamp,
+ Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras,
+ long lastChangedTimestamp,
int flags, int iconResId, String iconResName, String bitmapPath) {
mUserId = userId;
mId = id;
@@ -1773,8 +1849,8 @@
mDisabledMessageResId = disabledMessageResId;
mDisabledMessageResName = disabledMessageResName;
mCategories = cloneCategories(categories);
- mIntent = intent;
- mIntentPersistableExtras = intentPersistableExtras;
+ mIntents = cloneIntents(intentsWithExtras);
+ fixUpIntentExtras();
mRank = rank;
mExtras = extras;
mLastChangedTimestamp = lastChangedTimestamp;
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index f994d7e..af56105 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -53,7 +53,8 @@
@NonNull String callingPackage, @NonNull String packageName,
@NonNull List<String> shortcutIds, int userId);
- public abstract Intent createShortcutIntent(int launcherUserId, @NonNull String callingPackage,
+ public abstract Intent[] createShortcutIntents(
+ int launcherUserId, @NonNull String callingPackage,
@NonNull String packageName, @NonNull String shortcutId, int userId);
public abstract void addListener(@NonNull ShortcutChangeListener listener);
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 74dcc07..f6e6ad6 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -36,6 +36,7 @@
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.Serializable;
+import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
@@ -2571,6 +2572,20 @@
return p;
}
+ /** @hide */
+ public final <T extends Parcelable> T[] readParcelableArray(ClassLoader loader,
+ Class<T> clazz) {
+ int N = readInt();
+ if (N < 0) {
+ return null;
+ }
+ T[] p = (T[]) Array.newInstance(clazz, N);
+ for (int i = 0; i < N; i++) {
+ p[i] = readParcelable(loader);
+ }
+ return p;
+ }
+
/**
* Read and return a new Serializable object from the parcel.
* @return the Serializable object, or null if the Serializable name
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d426dd0..59e3096 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -21800,10 +21800,13 @@
}
@Override
- public int startActivityAsPackage(String packageName, int userId, Intent intent,
+ public int startActivitiesAsPackage(String packageName, int userId, Intent[] intents,
Bundle bOptions) {
- Preconditions.checkNotNull(intent, "intent");
- final String resolvedType = intent.resolveTypeIfNeeded(mContext.getContentResolver());
+ Preconditions.checkNotNull(intents, "intents");
+ final String[] resolvedTypes = new String[intents.length];
+ for (int i = 0; i < intents.length; i++) {
+ resolvedTypes[i] = intents[i].resolveTypeIfNeeded(mContext.getContentResolver());
+ }
// UID of the package on user userId.
// "= 0" is needed because otherwise catch(RemoteException) would make it look like
@@ -21817,9 +21820,8 @@
}
synchronized (ActivityManagerService.this) {
- return startActivityInPackage(packageUid, packageName, intent, resolvedType,
- /*resultTo*/ null, /*resultWho*/ null, /*requestCode*/ 0, /*startFlags*/ 0,
- bOptions, userId, /*container*/ null, /*inTask*/ null);
+ return startActivitiesInPackage(packageUid, packageName, intents, resolvedTypes,
+ /*resultTo*/ null, bOptions, userId);
}
}
}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 1913b61..b4dd587 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -452,27 +452,28 @@
ensureShortcutPermission(callingPackage, userId);
}
- final Intent intent = mShortcutServiceInternal.createShortcutIntent(getCallingUserId(),
- callingPackage, packageName, shortcutId, userId);
- if (intent == null) {
+ final Intent[] intents = mShortcutServiceInternal.createShortcutIntents(
+ getCallingUserId(), callingPackage, packageName, shortcutId, userId);
+ if (intents == null || intents.length == 0) {
return false;
}
// Note the target activity doesn't have to be exported.
- intent.setSourceBounds(sourceBounds);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ // TODO Use sourceBounds
- return startShortcutIntentAsPublisher(
- intent, packageName, startActivityOptions, userId);
+ intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ return startShortcutIntentsAsPublisher(
+ intents, packageName, startActivityOptions, userId);
}
- private boolean startShortcutIntentAsPublisher(@NonNull Intent intent,
+ private boolean startShortcutIntentsAsPublisher(@NonNull Intent[] intents,
@NonNull String publisherPackage, Bundle startActivityOptions, int userId) {
final int code;
final long ident = injectClearCallingIdentity();
try {
- code = mActivityManagerInternal.startActivityAsPackage(publisherPackage,
- userId, intent, startActivityOptions);
+ code = mActivityManagerInternal.startActivitiesAsPackage(publisherPackage,
+ userId, intents, startActivityOptions);
if (code >= ActivityManager.START_SUCCESS) {
return true; // Success
} else {
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 7d57f33..51c9619 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -23,6 +23,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.ShortcutInfo;
import android.content.res.Resources;
+import android.os.Bundle;
import android.os.PersistableBundle;
import android.text.format.Formatter;
import android.util.ArrayMap;
@@ -63,7 +64,8 @@
private static final String TAG_VERIFY = ShortcutService.TAG + ".verify";
static final String TAG_ROOT = "package";
- private static final String TAG_INTENT_EXTRAS = "intent-extras";
+ private static final String TAG_INTENT_EXTRAS_LEGACY = "intent-extras";
+ private static final String TAG_INTENT = "intent";
private static final String TAG_EXTRAS = "extras";
private static final String TAG_SHORTCUT = "shortcut";
private static final String TAG_CATEGORIES = "categories";
@@ -82,7 +84,8 @@
private static final String ATTR_DISABLED_MESSAGE = "dmessage";
private static final String ATTR_DISABLED_MESSAGE_RES_ID = "dmessageid";
private static final String ATTR_DISABLED_MESSAGE_RES_NAME = "dmessagename";
- private static final String ATTR_INTENT = "intent";
+ private static final String ATTR_INTENT_LEGACY = "intent";
+ private static final String ATTR_INTENT_NO_EXTRA = "intent-base";
private static final String ATTR_RANK = "rank";
private static final String ATTR_TIMESTAMP = "timestamp";
private static final String ATTR_FLAGS = "flags";
@@ -1288,7 +1291,6 @@
si.getDisabledMessageResourceId());
ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_NAME,
si.getDisabledMessageResName());
- ShortcutService.writeAttr(out, ATTR_INTENT, si.getIntentNoExtras());
ShortcutService.writeAttr(out, ATTR_TIMESTAMP,
si.getLastChangedTimestamp());
if (forBackup) {
@@ -1317,9 +1319,16 @@
out.endTag(null, TAG_CATEGORIES);
}
}
+ final Intent[] intentsNoExtras = si.getIntentsNoExtras();
+ final PersistableBundle[] intentsExtras = si.getIntentPersistableExtrases();
+ final int numIntents = intentsNoExtras.length;
+ for (int i = 0; i < numIntents; i++) {
+ out.startTag(null, TAG_INTENT);
+ ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
+ ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
+ out.endTag(null, TAG_INTENT);
+ }
- ShortcutService.writeTagExtra(out, TAG_INTENT_EXTRAS,
- si.getIntentPersistableExtras());
ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
out.endTag(null, TAG_SHORTCUT);
@@ -1382,8 +1391,9 @@
String disabledMessage;
int disabledMessageResId;
String disabledMessageResName;
- Intent intent;
- PersistableBundle intentPersistableExtras = null;
+ Intent intentLegacy;
+ PersistableBundle intentPersistableExtrasLegacy = null;
+ ArrayList<Intent> intents = new ArrayList<>();
int rank;
PersistableBundle extras = null;
long lastChangedTimestamp;
@@ -1407,7 +1417,7 @@
ATTR_DISABLED_MESSAGE_RES_ID);
disabledMessageResName = ShortcutService.parseStringAttribute(parser,
ATTR_DISABLED_MESSAGE_RES_NAME);
- intent = ShortcutService.parseIntentAttribute(parser, ATTR_INTENT);
+ intentLegacy = ShortcutService.parseIntentAttributeNoDefault(parser, ATTR_INTENT_LEGACY);
rank = (int) ShortcutService.parseLongAttribute(parser, ATTR_RANK);
lastChangedTimestamp = ShortcutService.parseLongAttribute(parser, ATTR_TIMESTAMP);
flags = (int) ShortcutService.parseLongAttribute(parser, ATTR_FLAGS);
@@ -1429,8 +1439,11 @@
depth, type, tag));
}
switch (tag) {
- case TAG_INTENT_EXTRAS:
- intentPersistableExtras = PersistableBundle.restoreFromXml(parser);
+ case TAG_INTENT_EXTRAS_LEGACY:
+ intentPersistableExtrasLegacy = PersistableBundle.restoreFromXml(parser);
+ continue;
+ case TAG_INTENT:
+ intents.add(parseIntent(parser));
continue;
case TAG_EXTRAS:
extras = PersistableBundle.restoreFromXml(parser);
@@ -1453,15 +1466,53 @@
throw ShortcutService.throwForInvalidTag(depth, tag);
}
+ if (intentLegacy != null) {
+ // For the legacy file format which supported only one intent per shortcut.
+ ShortcutInfo.setIntentExtras(intentLegacy, intentPersistableExtrasLegacy);
+ intents.clear();
+ intents.add(intentLegacy);
+ }
+
return new ShortcutInfo(
userId, id, packageName, activityComponent, /* icon =*/ null,
title, titleResId, titleResName, text, textResId, textResName,
disabledMessage, disabledMessageResId, disabledMessageResName,
- categories, intent,
- intentPersistableExtras, rank, extras, lastChangedTimestamp, flags,
+ categories,
+ intents.toArray(new Intent[intents.size()]),
+ rank, extras, lastChangedTimestamp, flags,
iconResId, iconResName, bitmapPath);
}
+ private static Intent parseIntent(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+
+ Intent intent = ShortcutService.parseIntentAttribute(parser,
+ ATTR_INTENT_NO_EXTRA);
+
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final int depth = parser.getDepth();
+ final String tag = parser.getName();
+ if (ShortcutService.DEBUG_LOAD) {
+ Slog.d(TAG, String.format(" depth=%d type=%d name=%s",
+ depth, type, tag));
+ }
+ switch (tag) {
+ case TAG_EXTRAS:
+ ShortcutInfo.setIntentExtras(intent,
+ PersistableBundle.restoreFromXml(parser));
+ continue;
+ }
+ throw ShortcutService.throwForInvalidTag(depth, tag);
+ }
+ return intent;
+ }
+
@VisibleForTesting
List<ShortcutInfo> getAllShortcutsForTest() {
return new ArrayList<>(mShortcuts.values());
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
index 7f5d931..e7b66fc 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
@@ -171,7 +171,7 @@
final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION);
- final long lastUpdateTime = ShortcutService.parseIntAttribute(
+ final long lastUpdateTime = ShortcutService.parseLongAttribute(
parser, ATTR_LAST_UPDATE_TIME);
// When restoring from backup, it's always shadow.
diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java
index 3f302d6..2a2a4b2 100644
--- a/services/core/java/com/android/server/pm/ShortcutParser.java
+++ b/services/core/java/com/android/server/pm/ShortcutParser.java
@@ -127,6 +127,7 @@
ShortcutInfo currentShortcut = null;
Set<String> categories = null;
+ final ArrayList<Intent> intents = new ArrayList<>();
outer:
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -143,9 +144,15 @@
final ShortcutInfo si = currentShortcut;
currentShortcut = null; // Make sure to null out for the next iteration.
- if (si.getIntent() == null) {
- Log.e(TAG, "Shortcut " + si.getId() + " has no intent. Skipping it.");
- continue;
+ if (si.isEnabled()) {
+ if (intents.size() == 0) {
+ Log.e(TAG, "Shortcut " + si.getId() + " has no intent. Skipping it.");
+ continue;
+ }
+ } else {
+ // Just set the default intent to disabled shortcuts.
+ intents.clear();
+ intents.add(new Intent(Intent.ACTION_VIEW));
}
if (numShortcuts >= maxShortcuts) {
@@ -153,6 +160,23 @@
+ activityInfo.getComponentName() + ". Skipping the rest.");
return result;
}
+
+ // Same flag as what TaskStackBuilder adds.
+ intents.get(0).addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_CLEAR_TASK |
+ Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+ try {
+ si.setIntents(intents.toArray(new Intent[intents.size()]));
+ } catch (RuntimeException e) {
+ // This shouldn't happen because intents in XML can't have complicated
+ // extras, but just in case Intent.parseIntent() supports such a thing one
+ // day.
+ Log.e(TAG, "Shortcut's extras contain un-persistable values. Skipping it.");
+ continue;
+ }
+ intents.clear();
+
if (categories != null) {
si.setCategories(categories);
categories = null;
@@ -196,17 +220,12 @@
}
}
}
- if (!si.isEnabled()) {
- // Just set the default intent to disabled shortcuts.
- si.setIntent(new Intent(Intent.ACTION_VIEW));
- }
currentShortcut = si;
categories = null;
continue;
}
if (depth == 3 && TAG_INTENT.equals(tag)) {
if ((currentShortcut == null)
- || (currentShortcut.getIntentNoExtras() != null)
|| !currentShortcut.isEnabled()) {
Log.e(TAG, "Ignoring excessive intent tag.");
continue;
@@ -216,17 +235,10 @@
parser, attrs);
if (TextUtils.isEmpty(intent.getAction())) {
Log.e(TAG, "Shortcut intent action must be provided. activity=" + activity);
+ currentShortcut = null; // Invalidate the current shortcut.
continue;
}
- try {
- currentShortcut.setIntent(intent);
- } catch (RuntimeException e) {
- // This shouldn't happen because intents in XML can't have complicated
- // extras, but just in case Intent.parseIntent() supports such a thing one
- // day.
- Log.e(TAG, "Shortcut's extras contain un-persistable values. Skipping it.");
- continue;
- }
+ intents.add(intent);
continue;
}
if (depth == 3 && TAG_CATEGORIES.equals(tag)) {
@@ -345,7 +357,6 @@
null, // disabled message res name
null, // categories
null, // intent
- null, // intent extras
rank,
null, // extras
service.injectCurrentTimeMillis(),
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index a91e284..e6a9739 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -687,7 +687,7 @@
}
@Nullable
- static Intent parseIntentAttribute(XmlPullParser parser, String attribute) {
+ static Intent parseIntentAttributeNoDefault(XmlPullParser parser, String attribute) {
final String value = parseStringAttribute(parser, attribute);
Intent parsed = null;
if (!TextUtils.isEmpty(value)) {
@@ -697,6 +697,12 @@
Slog.e(TAG, "Error parsing intent", e);
}
}
+ return parsed;
+ }
+
+ @Nullable
+ static Intent parseIntentAttribute(XmlPullParser parser, String attribute) {
+ Intent parsed = parseIntentAttributeNoDefault(parser, attribute);
if (parsed == null) {
// Default intent.
parsed = new Intent(Intent.ACTION_VIEW);
@@ -2240,7 +2246,7 @@
}
@Override
- public Intent createShortcutIntent(int launcherUserId,
+ public Intent[] createShortcutIntents(int launcherUserId,
@NonNull String callingPackage,
@NonNull String packageName, @NonNull String shortcutId, int userId) {
// Calling permission must be checked by LauncherAppsImpl.
@@ -2259,7 +2265,7 @@
Log.e(TAG, "Shortcut " + shortcutId + " does not exist or disabled");
return null;
}
- return si.getIntent();
+ return si.getIntents();
}
}
diff --git a/services/tests/servicestests/res/xml/shortcut_error_4.xml b/services/tests/servicestests/res/xml/shortcut_error_4.xml
index 3697bb4..f680e99 100644
--- a/services/tests/servicestests/res/xml/shortcut_error_4.xml
+++ b/services/tests/servicestests/res/xml/shortcut_error_4.xml
@@ -19,7 +19,9 @@
android:shortcutId="ms1"
android:shortcutShortLabel="@string/shortcut_title1"
>
- <intent android:action="action1" />
+ <intent android:action="action1" >
+ <extra android:name="key1" android:value="value1" />
+ </intent>
</shortcut>
<!-- Invalid: no intent -->
@@ -28,13 +30,17 @@
android:shortcutShortLabel="@string/shortcut_title1"
/>
- <!-- Valid: more than one intent; first one will be picked. -->
+ <!-- Valid: more than one intent -->
<shortcut
android:shortcutId="ms2"
android:shortcutShortLabel="@string/shortcut_title1"
>
- <intent android:action="action2_1" />
- <intent android:action="action2_2" />
+ <intent android:action="action2_1" >
+ <extra android:name="key1" android:value="value1" />
+ </intent>
+ <intent android:action="action2_2">
+ <extra android:name="key2" android:value="value2" />
+ </intent>
</shortcut>
<!-- Valid: disabled shortcut doesn't need an intent -->
@@ -53,11 +59,12 @@
<intent android:action="action4" />
</shortcut>
- <!-- Invalid, no intent action -->
+ <!-- Invalid, no intent action (if any of the intents is invalid, the entire shortcut will be invalid.) -->
<shortcut
android:shortcutId="ms_ignored2"
android:shortcutShortLabel="@string/shortcut_title1"
>
<intent android:data="x"/>
+ <intent android:action="actionx"/>
</shortcut>
</shortcuts>
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 5864255..ff1c3b6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -34,14 +34,12 @@
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.Activity;
-import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.IUidObserver;
import android.app.usage.UsageStatsManagerInternal;
+import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -190,11 +188,6 @@
}
@Override
- public void startActivityAsUser(@RequiresPermission Intent intent, @Nullable Bundle options,
- UserHandle userId) {
- }
-
- @Override
public int getUserId() {
return UserHandle.USER_SYSTEM;
}
@@ -706,17 +699,6 @@
mUnlockedUsers.put(USER_11, true);
mUnlockedUsers.put(USER_P0, true);
- // Set up mMockActivityManagerInternal.
- // By default, startActivityAsPackage() will simply forward to startActivityAsUser().
- doAnswer(new AnswerWithSystemCheck<>(inv -> {
- mServiceContext.startActivityAsUser(
- (Intent) inv.getArguments()[2],
- (Bundle) inv.getArguments()[3],
- UserHandle.of((Integer) inv.getArguments()[1]));
- return ActivityManager.START_SUCCESS;
- })).when(mMockActivityManagerInternal).startActivityAsPackage(anyString(), anyInt(),
- any(Intent.class), any(Bundle.class));
-
// Set up resources
setUpAppResources();
@@ -1303,6 +1285,35 @@
return s;
}
+ protected ShortcutInfo makeShortcutWithIntents(String id, Intent... intents) {
+ return makeShortcut(
+ id, "Title-" + id, /* activity =*/ null, /* icon =*/ null,
+ intents, /* rank =*/ 0);
+ }
+
+ /**
+ * Make a shortcut with details.
+ */
+ protected ShortcutInfo makeShortcut(String id, String title, ComponentName activity,
+ Icon icon, Intent[] intents, int rank) {
+ final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mClientContext, id)
+ .setActivity(new ComponentName(mClientContext.getPackageName(), "dummy"))
+ .setShortLabel(title)
+ .setRank(rank)
+ .setIntents(intents);
+ if (icon != null) {
+ b.setIcon(icon);
+ }
+ if (activity != null) {
+ b.setActivity(activity);
+ }
+ final ShortcutInfo s = b.build();
+
+ s.setTimestamp(mInjectedCurrentTimeMillis); // HACK
+
+ return s;
+ }
+
/**
* Make a shortcut with details.
*/
@@ -1388,33 +1399,53 @@
assertTrue(getPackageShortcut(packageName, shortcutId, userId) == null);
}
+ protected Intent[] launchShortcutAndGetIntentsInner(Runnable shortcutStarter,
+ @NonNull String packageName, @NonNull String shortcutId, int userId) {
+ reset(mMockActivityManagerInternal);
+ shortcutStarter.run();
+
+ final ArgumentCaptor<Intent[]> intentsCaptor = ArgumentCaptor.forClass(Intent[].class);
+ verify(mMockActivityManagerInternal).startActivitiesAsPackage(
+ eq(packageName),
+ eq(userId),
+ intentsCaptor.capture(),
+ any(Bundle.class));
+ return intentsCaptor.getValue();
+ }
+
+ protected Intent[] launchShortcutAndGetIntents(
+ @NonNull String packageName, @NonNull String shortcutId, int userId) {
+ return launchShortcutAndGetIntentsInner(
+ () -> {
+ mLauncherApps.startShortcut(packageName, shortcutId, null, null,
+ UserHandle.of(userId));
+ }, packageName, shortcutId, userId
+ );
+ }
+
protected Intent launchShortcutAndGetIntent(
@NonNull String packageName, @NonNull String shortcutId, int userId) {
- reset(mServiceContext);
- mLauncherApps.startShortcut(packageName, shortcutId, null, null,
- UserHandle.of(userId));
+ final Intent[] intents = launchShortcutAndGetIntents(packageName, shortcutId, userId);
+ assertEquals(1, intents.length);
+ return intents[0];
+ }
- final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mServiceContext).startActivityAsUser(
- intentCaptor.capture(),
- any(Bundle.class),
- eq(UserHandle.of(userId)));
- return intentCaptor.getValue();
+ protected Intent[] launchShortcutAndGetIntents_withShortcutInfo(
+ @NonNull String packageName, @NonNull String shortcutId, int userId) {
+ return launchShortcutAndGetIntentsInner(
+ () -> {
+ mLauncherApps.startShortcut(
+ getShortcutInfoAsLauncher(packageName, shortcutId, userId), null, null);
+ }, packageName, shortcutId, userId
+ );
}
protected Intent launchShortcutAndGetIntent_withShortcutInfo(
@NonNull String packageName, @NonNull String shortcutId, int userId) {
- reset(mServiceContext);
-
- mLauncherApps.startShortcut(
- getShortcutInfoAsLauncher(packageName, shortcutId, userId), null, null);
-
- final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mServiceContext).startActivityAsUser(
- intentCaptor.capture(),
- any(Bundle.class),
- eq(UserHandle.of(userId)));
- return intentCaptor.getValue();
+ final Intent[] intents = launchShortcutAndGetIntents_withShortcutInfo(
+ packageName, shortcutId, userId);
+ assertEquals(1, intents.length);
+ return intents[0];
}
protected void assertShortcutLaunchable(@NonNull String packageName, @NonNull String shortcutId,
@@ -1423,9 +1454,25 @@
assertNotNull(launchShortcutAndGetIntent_withShortcutInfo(packageName, shortcutId, userId));
}
- protected void assertShortcutNotLaunchable(@NonNull String packageName,
+ protected void assertShortcutNotLaunched(@NonNull String packageName,
+ @NonNull String shortcutId, int userId) {
+ reset(mMockActivityManagerInternal);
+ try {
+ mLauncherApps.startShortcut(packageName, shortcutId, null, null,
+ UserHandle.of(userId));
+ fail("ActivityNotFoundException was not thrown");
+ } catch (ActivityNotFoundException expected) {
+ }
+ // This shouldn't have been called.
+ verify(mMockActivityManagerInternal, times(0)).startActivitiesAsPackage(
+ anyString(),
+ anyInt(),
+ any(Intent[].class),
+ any(Bundle.class));
+ }
+
+ protected void assertStartShortcutThrowsException(@NonNull String packageName,
@NonNull String shortcutId, int userId, Class<?> expectedException) {
- reset(mServiceContext);
Exception thrown = null;
try {
mLauncherApps.startShortcut(packageName, shortcutId, null, null,
@@ -1433,11 +1480,6 @@
} catch (Exception e) {
thrown = e;
}
- // This shouldn't have been called.
- verify(mServiceContext, times(0)).startActivityAsUser(
- any(Intent.class),
- any(Bundle.class),
- any(UserHandle.class));
assertNotNull("Exception was not thrown", thrown);
assertEquals("Exception type different", expectedException, thrown.getClass());
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 55fa625f..5206507 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -58,10 +58,12 @@
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.Manifest.permission;
import android.app.ActivityManager;
@@ -1681,7 +1683,7 @@
.areAllPinned()
.areAllNotWithKeyFieldsOnly()
.areAllDisabled();
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_0,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s2", USER_0,
ActivityNotFoundException.class);
// Here, s4 is still enabled and launchable, but s3 is disabled.
@@ -1698,7 +1700,7 @@
.selectByIds("s4")
.areAllEnabled();
- assertShortcutNotLaunchable(CALLING_PACKAGE_2, "s3", USER_0,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_2, "s3", USER_0,
ActivityNotFoundException.class);
assertShortcutLaunchable(CALLING_PACKAGE_2, "s4", USER_0);
@@ -2130,17 +2132,17 @@
assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s1", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s2", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s3", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s4", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s5", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s6", USER_10,
SecurityException.class);
});
runWithCaller(LAUNCHER_1, USER_P0, () -> {
@@ -2178,17 +2180,17 @@
assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s1", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s2", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s3", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s4", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s5", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s6", USER_10,
SecurityException.class);
});
runWithCaller(LAUNCHER_2, USER_P0, () -> {
@@ -2226,17 +2228,17 @@
assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s1", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s2", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s3", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s4", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s5", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s6", USER_10,
SecurityException.class);
});
runWithCaller(LAUNCHER_2, USER_10, () -> {
@@ -2297,26 +2299,26 @@
"s1", "s2", "s3");
assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_0,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s2", USER_0,
ActivityNotFoundException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_0,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s3", USER_0,
ActivityNotFoundException.class);
assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s1", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s2", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s3", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s4", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s5", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s6", USER_10,
SecurityException.class);
});
runWithCaller(LAUNCHER_1, USER_P0, () -> {
@@ -2348,24 +2350,24 @@
assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
assertShortcutLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_0,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s3", USER_0,
ActivityNotFoundException.class);
assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s1", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s2", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s3", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s4", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s5", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s6", USER_10,
SecurityException.class);
});
runWithCaller(LAUNCHER_2, USER_P0, () -> {
@@ -2396,26 +2398,26 @@
"s1", "s3");
assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_0,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s2", USER_0,
ActivityNotFoundException.class);
assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_2, "s2", USER_0,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_2, "s2", USER_0,
ActivityNotFoundException.class);
assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s1", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s2", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s3", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s4", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s5", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s6", USER_10,
SecurityException.class);
});
runWithCaller(LAUNCHER_2, USER_10, () -> {
@@ -2432,28 +2434,28 @@
/* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_10)),
"s1", "s2", "s3");
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_0,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s1", USER_0,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_0,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s2", USER_0,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_0,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s3", USER_0,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_2, "s1", USER_0,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_2, "s1", USER_0,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_2, "s2", USER_0,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_2, "s2", USER_0,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_2, "s3", USER_0,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_2, "s3", USER_0,
SecurityException.class);
assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
assertShortcutLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s4", USER_10,
ActivityNotFoundException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s5", USER_10,
ActivityNotFoundException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s6", USER_10,
ActivityNotFoundException.class);
});
@@ -2490,26 +2492,26 @@
"s1", "s2", "s3");
assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_0,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s2", USER_0,
ActivityNotFoundException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_0,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s3", USER_0,
ActivityNotFoundException.class);
assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s1", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s2", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s3", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s4", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s5", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s6", USER_10,
SecurityException.class);
});
runWithCaller(LAUNCHER_1, USER_P0, () -> {
@@ -2541,24 +2543,24 @@
assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
assertShortcutLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_0,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s3", USER_0,
ActivityNotFoundException.class);
assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s1", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s2", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s3", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s4", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s5", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s6", USER_10,
SecurityException.class);
});
runWithCaller(LAUNCHER_2, USER_P0, () -> {
@@ -2589,26 +2591,26 @@
"s1", "s3");
assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_0,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s2", USER_0,
ActivityNotFoundException.class);
assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_2, "s2", USER_0,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_2, "s2", USER_0,
ActivityNotFoundException.class);
assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s1", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s2", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s3", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s4", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s5", USER_10,
SecurityException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10,
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s6", USER_10,
SecurityException.class);
});
}
@@ -2680,10 +2682,8 @@
assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
- assertShortcutNotLaunchable("no-such-package", "s2", USER_0,
- ActivityNotFoundException.class);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "xxxx", USER_0,
- ActivityNotFoundException.class);
+ assertShortcutNotLaunched("no-such-package", "s2", USER_0);
+ assertShortcutNotLaunched(CALLING_PACKAGE_1, "xxxx", USER_0);
});
// LAUNCHER_1 is no longer the default launcher
@@ -2710,19 +2710,18 @@
// Test inner errors.
runWithCaller(LAUNCHER_1, USER_0, () -> {
// Not launchable.
- doAnswer(new AnswerWithSystemCheck<>(inv -> {
- return ActivityManager.START_CLASS_NOT_FOUND;
- })).when(mMockActivityManagerInternal).startActivityAsPackage(anyString(), anyInt(),
- any(Intent.class), any(Bundle.class));
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_0,
+ doReturn(ActivityManager.START_CLASS_NOT_FOUND)
+ .when(mMockActivityManagerInternal).startActivitiesAsPackage(
+ anyString(), anyInt(), any(Intent[].class), any(Bundle.class));
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s1", USER_0,
ActivityNotFoundException.class);
// Still not launchable.
- doAnswer(new AnswerWithSystemCheck<>(inv -> {
- return ActivityManager.START_PERMISSION_DENIED;
- })).when(mMockActivityManagerInternal).startActivityAsPackage(anyString(), anyInt(),
- any(Intent.class), any(Bundle.class));
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_0,
+ doReturn(ActivityManager.START_CLASS_NOT_FOUND)
+ .when(mMockActivityManagerInternal)
+ .startActivitiesAsPackage(
+ anyString(), anyInt(), any(Intent[].class), any(Bundle.class));
+ assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s1", USER_0,
ActivityNotFoundException.class);
});
@@ -5924,11 +5923,41 @@
.areAllEnabled()
.forShortcutWithId("ms1", si -> {
assertTrue(si.isEnabled());
+ assertEquals(1, si.getIntents().length);
+
assertEquals("action1", si.getIntent().getAction());
+ assertEquals("value1", si.getIntent().getStringExtra("key1"));
+ assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_CLEAR_TASK |
+ Intent.FLAG_ACTIVITY_TASK_ON_HOME, si.getIntent().getFlags());
+
+ assertEquals("action1", si.getIntents()[0].getAction());
+ assertEquals("value1", si.getIntents()[0].getStringExtra("key1"));
+ assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_CLEAR_TASK |
+ Intent.FLAG_ACTIVITY_TASK_ON_HOME, si.getIntents()[0].getFlags());
})
.forShortcutWithId("ms2", si -> {
assertTrue(si.isEnabled());
- assertEquals("action2_1", si.getIntent().getAction());
+ assertEquals(2, si.getIntents().length);
+
+ // getIntent will return the last one.
+ assertEquals("action2_2", si.getIntent().getAction());
+ assertEquals("value2", si.getIntent().getStringExtra("key2"));
+ assertEquals(0, si.getIntent().getFlags());
+
+ final Intent i1 = si.getIntents()[0];
+ final Intent i2 = si.getIntents()[1];
+
+ assertEquals("action2_1", i1.getAction());
+ assertEquals("value1", i1.getStringExtra("key1"));
+ assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_CLEAR_TASK |
+ Intent.FLAG_ACTIVITY_TASK_ON_HOME, i1.getFlags());
+
+ assertEquals("action2_2", i2.getAction());
+ assertEquals("value2", i2.getStringExtra("key2"));
+ assertEquals(0, i2.getFlags());
});
});
@@ -6001,7 +6030,8 @@
assertEquals(si.getId(), "action1", si.getIntent().getAction());
})
.forShortcutWithId("ms2", si -> {
- assertEquals(si.getId(), "action2_1", si.getIntent().getAction());
+ // getIntent returns the last one.
+ assertEquals(si.getId(), "action2_2", si.getIntent().getAction());
})
.forShortcutWithId("ms3", si -> {
assertEquals(si.getId(), Intent.ACTION_VIEW, si.getIntent().getAction());
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index efd380b..f97355a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -90,7 +90,7 @@
assertExpectException(
RuntimeException.class,
- "intent cannot be null",
+ "intents cannot contain null",
() -> new ShortcutInfo.Builder(getTestContext(), "id").setIntent(null));
assertExpectException(
@@ -879,11 +879,16 @@
final long now = mInjectedCurrentTimeMillis;
mInjectedCurrentTimeMillis += 1;
+ dumpsysOnLogcat("before save");
+
// Save and load.
mService.saveDirtyInfo();
initService();
mService.handleUnlockUser(USER_10);
+ dumpUserFile(USER_10);
+ dumpsysOnLogcat("after load");
+
ShortcutInfo si;
si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_10);
@@ -912,6 +917,8 @@
// to test it.
si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_10);
assertEquals(1, si.getRank());
+
+ dumpUserFile(USER_10);
}
public void testShortcutInfoSaveAndLoad_resId() throws InterruptedException {
@@ -1133,7 +1140,30 @@
assertEquals(intent.getAction(), si.getIntent().getAction());
assertEquals(intent.getData(), si.getIntent().getData());
assertEquals(intent.getComponent(), si.getIntent().getComponent());
- assertBundlesEqual(intent.getExtras(), si.getExtras());
+ assertBundlesEqual(intent.getExtras(), si.getIntent().getExtras());
+ });
+ }
+
+ private void checkShortcutInfoSaveAndLoad_intents(Intent... intents) {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcutWithIntents("s1", intents))));
+ initService();
+ mService.handleUnlockUser(USER_0);
+
+ assertWith(getCallerShortcuts())
+ .haveIds("s1")
+ .forShortcutWithId("s1", si -> {
+
+ final Intent[] actual = si.getIntents();
+ assertEquals(intents.length, actual.length);
+
+ for (int i = 0; i < intents.length; i++) {
+ assertEquals(intents[i].getAction(), actual[i].getAction());
+ assertEquals(intents[i].getData(), actual[i].getData());
+ assertEquals(intents[i].getComponent(), actual[i].getComponent());
+ assertEquals(intents[i].getFlags(), actual[i].getFlags());
+ assertBundlesEqual(intents[i].getExtras(), actual[i].getExtras());
+ }
});
}
@@ -1176,6 +1206,30 @@
.putExtras(makeBundle("a", "b")));
mInjectedCurrentTimeMillis += INTERVAL; // reset throttling.
+
+ // Multi-intents
+ checkShortcutInfoSaveAndLoad_intents(
+ new Intent(Intent.ACTION_MAIN).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK),
+ new Intent(Intent.ACTION_VIEW).setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
+ );
+
+ checkShortcutInfoSaveAndLoad_intents(
+ new Intent(Intent.ACTION_MAIN).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ .setComponent(new ComponentName("a", "b")),
+ new Intent(Intent.ACTION_VIEW)
+ .setComponent(new ComponentName("a", "b"))
+ );
+
+ checkShortcutInfoSaveAndLoad_intents(
+ new Intent(Intent.ACTION_MAIN)
+ .setComponent(new ComponentName("a", "b")),
+ new Intent(Intent.ACTION_VIEW).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ .setComponent(new ComponentName("a", "b")),
+ new Intent("xyz").setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK
+ | Intent.FILL_IN_COMPONENT)
+ .setComponent(new ComponentName("a", "b")).putExtras(
+ makeBundle("xx", "yy"))
+ );
}
public void testThrottling() {
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest4.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest4.java
index 54c4b22..583c3d4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest4.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest4.java
@@ -100,7 +100,7 @@
);
public void testPersistingWeirdCharacters() {
- final Intent intent = new Intent(Intent.ACTION_VIEW)
+ final Intent intent = new Intent(Intent.ACTION_MAIN)
.putExtras(sIntentExtras);
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
index 2f64ad7..ce7adfa 100644
--- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
+++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
@@ -861,8 +861,8 @@
if (b1 == null && b2 == null) {
return; // pass
}
- assertNotNull(b1);
- assertNotNull(b2);
+ assertNotNull("b1 is null but b2 is not", b1);
+ assertNotNull("b2 is null but b1 is not", b2);
// HashSet makes the error message readable.
assertEquals(set(b1.keySet()), set(b2.keySet()));
@@ -973,7 +973,8 @@
waitOnMainThread();
- launcherApps.unregisterCallback(asserter.getMockCallback());
+ // TODO unregister doesn't work well during unit tests. Figure out and fix it.
+ // launcherApps.unregisterCallback(asserter.getMockCallback());
return asserter;
}