Update manifest shortcut XML schema
Use the standard <intent> tag instead of custom tags.
- Also fix setDynamicShortcuts(), which was broken in the previous CL.
- Also tolerate runtime exceptions while parsing XMLs
- Also fix b/29422018 while I'm at it
Bug 29390156
Bug 29077932
Bug 29422018
Change-Id: I2756c9d66c6d7b2962a982d9e57a7d84a5755b28
diff --git a/api/current.txt b/api/current.txt
index ae6a8bc..57f1f65 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -420,9 +420,9 @@
field public static final int contentInsetStart = 16843859; // 0x1010453
field public static final int contentInsetStartWithNavigation = 16844066; // 0x1010522
field public static final int contextClickable = 16844007; // 0x10104e7
- field public static final int contextDescription = 16844082; // 0x1010532
+ field public static final int contextDescription = 16844078; // 0x101052e
field public static final int contextPopupMenuStyle = 16844033; // 0x1010501
- field public static final int contextUri = 16844081; // 0x1010531
+ field public static final int contextUri = 16844077; // 0x101052d
field public static final int controlX1 = 16843772; // 0x10103fc
field public static final int controlX2 = 16843774; // 0x10103fe
field public static final int controlY1 = 16843773; // 0x10103fd
@@ -1041,7 +1041,7 @@
field public static final int rotation = 16843558; // 0x1010326
field public static final int rotationX = 16843559; // 0x1010327
field public static final int rotationY = 16843560; // 0x1010328
- field public static final int roundIcon = 16844080; // 0x1010530
+ field public static final int roundIcon = 16844076; // 0x101052c
field public static final int rowCount = 16843637; // 0x1010375
field public static final int rowDelay = 16843216; // 0x10101d0
field public static final int rowEdgeFlags = 16843329; // 0x1010241
@@ -1109,20 +1109,16 @@
field public static final int shareInterpolator = 16843195; // 0x10101bb
field public static final int sharedUserId = 16842763; // 0x101000b
field public static final int sharedUserLabel = 16843361; // 0x1010261
- field public static final int shortcutCategories = 16844077; // 0x101052d
- field public static final int shortcutDisabledMessage = 16844076; // 0x101052c
- field public static final int shortcutIcon = 16844073; // 0x1010529
+ field public static final int shortcutDisabledMessage = 16844075; // 0x101052b
field public static final int shortcutId = 16844072; // 0x1010528
- field public static final int shortcutIntentAction = 16844078; // 0x101052e
- field public static final int shortcutIntentData = 16844079; // 0x101052f
- field public static final int shortcutLongLabel = 16844075; // 0x101052b
- field public static final int shortcutShortLabel = 16844074; // 0x101052a
+ field public static final int shortcutLongLabel = 16844074; // 0x101052a
+ field public static final int shortcutShortLabel = 16844073; // 0x1010529
field public static final int shouldDisableView = 16843246; // 0x10101ee
field public static final int showAsAction = 16843481; // 0x10102d9
field public static final int showDefault = 16843258; // 0x10101fa
field public static final int showDividers = 16843561; // 0x1010329
field public static final int showForAllUsers = 16844015; // 0x10104ef
- field public static final int showMetadataInPreview = 16844083; // 0x1010533
+ field public static final int showMetadataInPreview = 16844079; // 0x101052f
field public static final deprecated int showOnLockScreen = 16843721; // 0x10103c9
field public static final int showSilent = 16843259; // 0x10101fb
field public static final int showText = 16843949; // 0x10104ad
@@ -10099,14 +10095,14 @@
method public android.content.pm.ShortcutInfo build();
method public android.content.pm.ShortcutInfo.Builder setActivity(android.content.ComponentName);
method public android.content.pm.ShortcutInfo.Builder setCategories(java.util.Set<java.lang.String>);
- method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.String);
+ method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.CharSequence);
method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle);
method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon);
method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
- method public android.content.pm.ShortcutInfo.Builder setLongLabel(java.lang.String);
+ method public android.content.pm.ShortcutInfo.Builder setLongLabel(java.lang.CharSequence);
method public android.content.pm.ShortcutInfo.Builder setRank(int);
- method public android.content.pm.ShortcutInfo.Builder setShortLabel(java.lang.String);
+ method public android.content.pm.ShortcutInfo.Builder setShortLabel(java.lang.CharSequence);
}
public class ShortcutManager {
diff --git a/api/system-current.txt b/api/system-current.txt
index 15e8673..0f37751 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -527,9 +527,9 @@
field public static final int contentInsetStart = 16843859; // 0x1010453
field public static final int contentInsetStartWithNavigation = 16844066; // 0x1010522
field public static final int contextClickable = 16844007; // 0x10104e7
- field public static final int contextDescription = 16844082; // 0x1010532
+ field public static final int contextDescription = 16844078; // 0x101052e
field public static final int contextPopupMenuStyle = 16844033; // 0x1010501
- field public static final int contextUri = 16844081; // 0x1010531
+ field public static final int contextUri = 16844077; // 0x101052d
field public static final int controlX1 = 16843772; // 0x10103fc
field public static final int controlX2 = 16843774; // 0x10103fe
field public static final int controlY1 = 16843773; // 0x10103fd
@@ -1148,7 +1148,7 @@
field public static final int rotation = 16843558; // 0x1010326
field public static final int rotationX = 16843559; // 0x1010327
field public static final int rotationY = 16843560; // 0x1010328
- field public static final int roundIcon = 16844080; // 0x1010530
+ field public static final int roundIcon = 16844076; // 0x101052c
field public static final int rowCount = 16843637; // 0x1010375
field public static final int rowDelay = 16843216; // 0x10101d0
field public static final int rowEdgeFlags = 16843329; // 0x1010241
@@ -1220,20 +1220,16 @@
field public static final int shareInterpolator = 16843195; // 0x10101bb
field public static final int sharedUserId = 16842763; // 0x101000b
field public static final int sharedUserLabel = 16843361; // 0x1010261
- field public static final int shortcutCategories = 16844077; // 0x101052d
- field public static final int shortcutDisabledMessage = 16844076; // 0x101052c
- field public static final int shortcutIcon = 16844073; // 0x1010529
+ field public static final int shortcutDisabledMessage = 16844075; // 0x101052b
field public static final int shortcutId = 16844072; // 0x1010528
- field public static final int shortcutIntentAction = 16844078; // 0x101052e
- field public static final int shortcutIntentData = 16844079; // 0x101052f
- field public static final int shortcutLongLabel = 16844075; // 0x101052b
- field public static final int shortcutShortLabel = 16844074; // 0x101052a
+ field public static final int shortcutLongLabel = 16844074; // 0x101052a
+ field public static final int shortcutShortLabel = 16844073; // 0x1010529
field public static final int shouldDisableView = 16843246; // 0x10101ee
field public static final int showAsAction = 16843481; // 0x10102d9
field public static final int showDefault = 16843258; // 0x10101fa
field public static final int showDividers = 16843561; // 0x1010329
field public static final int showForAllUsers = 16844015; // 0x10104ef
- field public static final int showMetadataInPreview = 16844083; // 0x1010533
+ field public static final int showMetadataInPreview = 16844079; // 0x101052f
field public static final deprecated int showOnLockScreen = 16843721; // 0x10103c9
field public static final int showSilent = 16843259; // 0x10101fb
field public static final int showText = 16843949; // 0x10104ad
@@ -10523,14 +10519,14 @@
method public android.content.pm.ShortcutInfo build();
method public android.content.pm.ShortcutInfo.Builder setActivity(android.content.ComponentName);
method public android.content.pm.ShortcutInfo.Builder setCategories(java.util.Set<java.lang.String>);
- method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.String);
+ method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.CharSequence);
method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle);
method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon);
method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
- method public android.content.pm.ShortcutInfo.Builder setLongLabel(java.lang.String);
+ method public android.content.pm.ShortcutInfo.Builder setLongLabel(java.lang.CharSequence);
method public android.content.pm.ShortcutInfo.Builder setRank(int);
- method public android.content.pm.ShortcutInfo.Builder setShortLabel(java.lang.String);
+ method public android.content.pm.ShortcutInfo.Builder setShortLabel(java.lang.CharSequence);
}
public class ShortcutManager {
diff --git a/api/test-current.txt b/api/test-current.txt
index 4bb7a79..ec10ccc 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -420,9 +420,9 @@
field public static final int contentInsetStart = 16843859; // 0x1010453
field public static final int contentInsetStartWithNavigation = 16844066; // 0x1010522
field public static final int contextClickable = 16844007; // 0x10104e7
- field public static final int contextDescription = 16844082; // 0x1010532
+ field public static final int contextDescription = 16844078; // 0x101052e
field public static final int contextPopupMenuStyle = 16844033; // 0x1010501
- field public static final int contextUri = 16844081; // 0x1010531
+ field public static final int contextUri = 16844077; // 0x101052d
field public static final int controlX1 = 16843772; // 0x10103fc
field public static final int controlX2 = 16843774; // 0x10103fe
field public static final int controlY1 = 16843773; // 0x10103fd
@@ -1041,7 +1041,7 @@
field public static final int rotation = 16843558; // 0x1010326
field public static final int rotationX = 16843559; // 0x1010327
field public static final int rotationY = 16843560; // 0x1010328
- field public static final int roundIcon = 16844080; // 0x1010530
+ field public static final int roundIcon = 16844076; // 0x101052c
field public static final int rowCount = 16843637; // 0x1010375
field public static final int rowDelay = 16843216; // 0x10101d0
field public static final int rowEdgeFlags = 16843329; // 0x1010241
@@ -1109,20 +1109,16 @@
field public static final int shareInterpolator = 16843195; // 0x10101bb
field public static final int sharedUserId = 16842763; // 0x101000b
field public static final int sharedUserLabel = 16843361; // 0x1010261
- field public static final int shortcutCategories = 16844077; // 0x101052d
- field public static final int shortcutDisabledMessage = 16844076; // 0x101052c
- field public static final int shortcutIcon = 16844073; // 0x1010529
+ field public static final int shortcutDisabledMessage = 16844075; // 0x101052b
field public static final int shortcutId = 16844072; // 0x1010528
- field public static final int shortcutIntentAction = 16844078; // 0x101052e
- field public static final int shortcutIntentData = 16844079; // 0x101052f
- field public static final int shortcutLongLabel = 16844075; // 0x101052b
- field public static final int shortcutShortLabel = 16844074; // 0x101052a
+ field public static final int shortcutLongLabel = 16844074; // 0x101052a
+ field public static final int shortcutShortLabel = 16844073; // 0x1010529
field public static final int shouldDisableView = 16843246; // 0x10101ee
field public static final int showAsAction = 16843481; // 0x10102d9
field public static final int showDefault = 16843258; // 0x10101fa
field public static final int showDividers = 16843561; // 0x1010329
field public static final int showForAllUsers = 16844015; // 0x10104ef
- field public static final int showMetadataInPreview = 16844083; // 0x1010533
+ field public static final int showMetadataInPreview = 16844079; // 0x101052f
field public static final deprecated int showOnLockScreen = 16843721; // 0x10103c9
field public static final int showSilent = 16843259; // 0x10101fb
field public static final int showText = 16843949; // 0x10104ad
@@ -10112,14 +10108,14 @@
method public android.content.pm.ShortcutInfo build();
method public android.content.pm.ShortcutInfo.Builder setActivity(android.content.ComponentName);
method public android.content.pm.ShortcutInfo.Builder setCategories(java.util.Set<java.lang.String>);
- method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.String);
+ method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.CharSequence);
method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle);
method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon);
method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
- method public android.content.pm.ShortcutInfo.Builder setLongLabel(java.lang.String);
+ method public android.content.pm.ShortcutInfo.Builder setLongLabel(java.lang.CharSequence);
method public android.content.pm.ShortcutInfo.Builder setRank(int);
- method public android.content.pm.ShortcutInfo.Builder setShortLabel(java.lang.String);
+ method public android.content.pm.ShortcutInfo.Builder setShortLabel(java.lang.CharSequence);
}
public class ShortcutManager {
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 896fa43..064b909 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -30,6 +30,7 @@
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
@@ -245,7 +246,7 @@
mTextResId = b.mTextResId;
mDisabledMessage = b.mDisabledMessage;
mDisabledMessageResId = b.mDisabledMessageResId;
- mCategories = clone(b.mCategories);
+ mCategories = cloneCategories(b.mCategories);
mIntent = b.mIntent;
if (mIntent != null) {
final Bundle intentExtras = mIntent.getExtras();
@@ -259,8 +260,17 @@
updateTimestamp();
}
- private <T> ArraySet<T> clone(Set<T> source) {
- return (source == null) ? null : new ArraySet<>(source);
+ private ArraySet<String> cloneCategories(Set<String> source) {
+ if (source == null) {
+ return null;
+ }
+ final ArraySet<String> ret = new ArraySet<>(source.size());
+ for (CharSequence s : source) {
+ if (!TextUtils.isEmpty(s)) {
+ ret.add(s.toString().intern());
+ }
+ }
+ return ret;
}
/**
@@ -304,7 +314,7 @@
mTextResId = source.mTextResId;
mDisabledMessage = source.mDisabledMessage;
mDisabledMessageResId = source.mDisabledMessageResId;
- mCategories = clone(source.mCategories);
+ mCategories = cloneCategories(source.mCategories);
if ((cloneFlags & CLONE_REMOVE_INTENT) == 0) {
mIntent = source.mIntent;
mIntentPersistableExtras = source.mIntentPersistableExtras;
@@ -614,7 +624,7 @@
mDisabledMessageResName = null;
}
if (source.mCategories != null) {
- mCategories = clone(source.mCategories);
+ mCategories = cloneCategories(source.mCategories);
}
if (source.mIntent != null) {
mIntent = source.mIntent;
@@ -752,7 +762,7 @@
* an icon. The recommend max length is 10 characters.
*/
@NonNull
- public Builder setShortLabel(@NonNull String shortLabel) {
+ public Builder setShortLabel(@NonNull CharSequence shortLabel) {
Preconditions.checkState(mTitleResId == 0, "shortLabelResId already set");
mTitle = Preconditions.checkStringNotEmpty(shortLabel, "shortLabel");
return this;
@@ -776,14 +786,14 @@
* The recommend max length is 25 characters.
*/
@NonNull
- public Builder setLongLabel(@NonNull String longLabel) {
+ public Builder setLongLabel(@NonNull CharSequence longLabel) {
Preconditions.checkState(mTextResId == 0, "longLabelResId already set");
mText = Preconditions.checkStringNotEmpty(longLabel, "longLabel");
return this;
}
/** @hide -- old signature, the internal code still uses it. */
- public Builder setTitle(@NonNull String value) {
+ public Builder setTitle(@NonNull CharSequence value) {
return setShortLabel(value);
}
@@ -793,7 +803,7 @@
}
/** @hide -- old signature, the internal code still uses it. */
- public Builder setText(@NonNull String value) {
+ public Builder setText(@NonNull CharSequence value) {
return setLongLabel(value);
}
@@ -813,7 +823,7 @@
}
@NonNull
- public Builder setDisabledMessage(@NonNull String disabledMessage) {
+ public Builder setDisabledMessage(@NonNull CharSequence disabledMessage) {
Preconditions.checkState(
mDisabledMessageResId == 0, "disabledMessageResId already set");
mDisabledMessage =
@@ -1355,6 +1365,37 @@
mIconResName = iconResName;
}
+ /**
+ * Replaces the intent
+ *
+ * @throws IllegalArgumentException when extra is not compatible with {@link PersistableBundle}.
+ *
+ * @hide
+ */
+ public void setIntent(Intent intent) throws IllegalArgumentException {
+ Preconditions.checkNotNull(intent);
+
+ final Bundle intentExtras = intent.getExtras();
+
+ mIntent = intent;
+
+ if (intentExtras != null) {
+ intent.replaceExtras((Bundle) null);
+ mIntentPersistableExtras = new PersistableBundle(intentExtras);
+ } else {
+ mIntentPersistableExtras = null;
+ }
+ }
+
+ /**
+ * Replaces the categories.
+ *
+ * @hide
+ */
+ public void setCategories(Set<String> categories) {
+ mCategories = cloneCategories(categories);
+ }
+
private ShortcutInfo(Parcel source) {
final ClassLoader cl = getClass().getClassLoader();
@@ -1591,7 +1632,7 @@
mDisabledMessage = disabledMessage;
mDisabledMessageResId = disabledMessageResId;
mDisabledMessageResName = disabledMessageResName;
- mCategories = clone(categories);
+ mCategories = cloneCategories(categories);
mIntent = intent;
mIntentPersistableExtras = intentPersistableExtras;
mRank = rank;
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d0c6a8e..13e1d00 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8251,12 +8251,13 @@
<declare-styleable name="Shortcut">
<attr name="shortcutId" format="string" />
<attr name="enabled" />
- <attr name="shortcutIcon" format="reference" />
+ <attr name="icon" />
<attr name="shortcutShortLabel" format="reference" />
<attr name="shortcutLongLabel" format="reference" />
<attr name="shortcutDisabledMessage" format="reference" />
- <attr name="shortcutCategories" format="string" />
- <attr name="shortcutIntentAction" format="string" />
- <attr name="shortcutIntentData" format="string" />
+ </declare-styleable>
+
+ <declare-styleable name="ShortcutCategories">
+ <attr name="name" />
</declare-styleable>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index da31767..c187d2c 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2733,13 +2733,9 @@
=============================================================== -->
<eat-comment />
<public type="attr" name="shortcutId" />
- <public type="attr" name="shortcutIcon" />
<public type="attr" name="shortcutShortLabel" />
<public type="attr" name="shortcutLongLabel" />
<public type="attr" name="shortcutDisabledMessage" />
- <public type="attr" name="shortcutCategories" />
- <public type="attr" name="shortcutIntentAction" />
- <public type="attr" name="shortcutIntentData" />
<public type="attr" name="roundIcon" />
<public type="attr" name="contextUri" />
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index ace14ac..d637586 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -682,6 +682,8 @@
changed |= pushOutExcessShortcuts();
}
+ s.verifyStates();
+
if (changed) {
// This will send a notification to the launcher, and also save .
s.packageShortcutsChanged(getPackageName(), getPackageUserId());
@@ -774,6 +776,7 @@
}
removeOrphans();
}
+ adjustRanks();
return changed;
}
@@ -810,7 +813,6 @@
deleteDynamicWithId(shortcut.getId());
}
}
- service.verifyStates();
return changed;
}
diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java
index ec19927..c349b75 100644
--- a/services/core/java/com/android/server/pm/ShortcutParser.java
+++ b/services/core/java/com/android/server/pm/ShortcutParser.java
@@ -24,10 +24,10 @@
import android.content.pm.ShortcutInfo;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
-import android.net.Uri;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.Slog;
import android.util.Xml;
@@ -48,10 +48,12 @@
private static final boolean DEBUG = ShortcutService.DEBUG || false; // DO NOT SUBMIT WITH TRUE
@VisibleForTesting
- static final String METADATA_KEY = "android.pm.Shortcuts";
+ static final String METADATA_KEY = "android.app.shortcuts";
private static final String TAG_SHORTCUTS = "shortcuts";
private static final String TAG_SHORTCUT = "shortcut";
+ private static final String TAG_INTENT = "intent";
+ private static final String TAG_CATEGORIES = "categories";
@Nullable
public static List<ShortcutInfo> parseShortcuts(ShortcutService service,
@@ -60,10 +62,17 @@
List<ShortcutInfo> result = null;
- if (pi != null && pi.activities != null) {
- for (ActivityInfo activityInfo : pi.activities) {
- result = parseShortcutsOneFile(service, activityInfo, packageName, userId, result);
+ try {
+ if (pi != null && pi.activities != null) {
+ for (ActivityInfo activityInfo : pi.activities) {
+ result = parseShortcutsOneFile(service, activityInfo, packageName, userId, result);
+ }
}
+ } catch (RuntimeException e) {
+ // Resource ID mismatch may cause various runtime exceptions when parsing XMLs.
+ service.wtf(
+ "Exception caught while parsing shortcut XML for package=" + packageName, e);
+ return null;
}
return result;
}
@@ -89,14 +98,58 @@
final int maxShortcuts = service.getMaxActivityShortcuts();
int numShortcuts = 0;
+ // We instantiate ShortcutInfo at <shortcut>, but we add it to the list at </shortcut>,
+ // after parsing <intent>. We keep the current one in here.
+ ShortcutInfo currentShortcut = null;
+
+ Set<String> categories = null;
+
outer:
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > 0)) {
+ final int depth = parser.getDepth();
+ final String tag = parser.getName();
+
+ // When a shortcut tag is closing, publish.
+ if ((type == XmlPullParser.END_TAG) && (depth == 2) && (TAG_SHORTCUT.equals(tag))) {
+ if (currentShortcut == null) {
+ // Shortcut was invalid.
+ continue;
+ }
+ 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 (numShortcuts >= maxShortcuts) {
+ Log.e(TAG, "More than " + maxShortcuts + " shortcuts found for "
+ + activityInfo.getComponentName() + ". Skipping the rest.");
+ return result;
+ }
+ if (categories != null) {
+ si.setCategories(categories);
+ categories = null;
+ }
+
+ if (result == null) {
+ result = new ArrayList<>();
+ }
+ result.add(si);
+ numShortcuts++;
+ rank++;
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, "Shortcut added: " + si.toInsecureString());
+ }
+ continue;
+ }
+
+ // Otherwise, just look at start tags.
if (type != XmlPullParser.START_TAG) {
continue;
}
- final int depth = parser.getDepth();
- final String tag = parser.getName();
if (depth == 1 && TAG_SHORTCUTS.equals(tag)) {
continue; // Root tag.
@@ -104,35 +157,73 @@
if (depth == 2 && TAG_SHORTCUT.equals(tag)) {
final ShortcutInfo si = parseShortcutAttributes(
service, attrs, packageName, activity, userId, rank);
+ if (si == null) {
+ // Shortcut was invalid.
+ continue;
+ }
if (ShortcutService.DEBUG) {
- Slog.d(TAG, "Shortcut=" + si);
+ Slog.d(TAG, "Shortcut found: " + si.toInsecureString());
}
if (result != null) {
for (int i = result.size() - 1; i >= 0; i--) {
if (si.getId().equals(result.get(i).getId())) {
- Slog.w(TAG, "Duplicate shortcut ID detected, skipping.");
+ Log.e(TAG, "Duplicate shortcut ID detected. Skipping it.");
continue outer;
}
}
}
+ 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;
+ }
- if (si != null) {
- if (numShortcuts >= maxShortcuts) {
- Slog.w(TAG, "More than " + maxShortcuts + " shortcuts found for "
- + activityInfo.getComponentName() + ", ignoring the rest.");
- return result;
- }
-
- if (result == null) {
- result = new ArrayList<>();
- }
- result.add(si);
- numShortcuts++;
- rank++;
+ final Intent intent = Intent.parseIntent(service.mContext.getResources(),
+ parser, attrs);
+ if (TextUtils.isEmpty(intent.getAction())) {
+ Log.e(TAG, "Shortcut intent action must be provided. activity=" + activity);
+ 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;
}
continue;
}
- Slog.w(TAG, "Unknown tag " + tag);
+ if (depth == 3 && TAG_CATEGORIES.equals(tag)) {
+ if ((currentShortcut == null)
+ || (currentShortcut.getCategories() != null)) {
+ continue;
+ }
+ final String name = parseCategories(service, attrs);
+ if (TextUtils.isEmpty(name)) {
+ Log.e(TAG, "Empty category found. activity=" + activity);
+ continue;
+ }
+
+ if (categories == null) {
+ categories = new ArraySet<>();
+ }
+ categories.add(name);
+ continue;
+ }
+
+ Log.w(TAG, "Unknown tag " + tag + " at depth " + depth);
}
} finally {
if (parser != null) {
@@ -142,6 +233,16 @@
return result;
}
+ private static String parseCategories(ShortcutService service, AttributeSet attrs) {
+ final TypedArray sa = service.mContext.getResources().obtainAttributes(attrs,
+ R.styleable.ShortcutCategories);
+ try {
+ return sa.getString(R.styleable.ShortcutCategories_name);
+ } finally {
+ sa.recycle();
+ }
+ }
+
private static ShortcutInfo parseShortcutAttributes(ShortcutService service,
AttributeSet attrs, String packageName, ComponentName activity,
@UserIdInt int userId, int rank) {
@@ -150,14 +251,11 @@
try {
final String id = sa.getString(R.styleable.Shortcut_shortcutId);
final boolean enabled = sa.getBoolean(R.styleable.Shortcut_enabled, true);
- final int iconResId = sa.getResourceId(R.styleable.Shortcut_shortcutIcon, 0);
+ final int iconResId = sa.getResourceId(R.styleable.Shortcut_icon, 0);
final int titleResId = sa.getResourceId(R.styleable.Shortcut_shortcutShortLabel, 0);
final int textResId = sa.getResourceId(R.styleable.Shortcut_shortcutLongLabel, 0);
final int disabledMessageResId = sa.getResourceId(
R.styleable.Shortcut_shortcutDisabledMessage, 0);
- final String categories = sa.getString(R.styleable.Shortcut_shortcutCategories);
- String intentAction = sa.getString(R.styleable.Shortcut_shortcutIntentAction);
- final String intentData = sa.getString(R.styleable.Shortcut_shortcutIntentData);
if (TextUtils.isEmpty(id)) {
Slog.w(TAG, "Shortcut ID must be provided. activity=" + activity);
@@ -167,31 +265,6 @@
Slog.w(TAG, "Shortcut title must be provided. activity=" + activity);
return null;
}
- if (TextUtils.isEmpty(intentAction)) {
- if (enabled) {
- Slog.w(TAG, "Shortcut intent action must be provided. activity=" + activity);
- return null;
- } else {
- // Disabled shortcut doesn't have to have an action, but just set VIEW as the
- // default.
- intentAction = Intent.ACTION_VIEW;
- }
- }
-
- final ArraySet<String> categoriesSet;
- if (categories == null) {
- categoriesSet = null;
- } else {
- final String[] arr = categories.split(":");
- categoriesSet = new ArraySet<>(arr.length);
- for (String v : arr) {
- categoriesSet.add(v);
- }
- }
- final Intent intent = new Intent(intentAction);
- if (!TextUtils.isEmpty(intentData)) {
- intent.setData(Uri.parse(intentData));
- }
return createShortcutFromManifest(
service,
@@ -202,8 +275,6 @@
titleResId,
textResId,
disabledMessageResId,
- categoriesSet,
- intent,
rank,
iconResId,
enabled);
@@ -214,8 +285,8 @@
private static ShortcutInfo createShortcutFromManifest(ShortcutService service,
@UserIdInt int userId, String id, String packageName, ComponentName activityComponent,
- int titleResId, int textResId, int disabledMessageResId, Set<String> categories,
- Intent intent, int rank, int iconResId, boolean enabled) {
+ int titleResId, int textResId, int disabledMessageResId,
+ int rank, int iconResId, boolean enabled) {
final int flags =
(enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED)
@@ -239,8 +310,8 @@
null, // disabled message string
disabledMessageResId,
null, // disabled message res name
- categories,
- intent,
+ null, // categories
+ null, // intent
null, // intent extras
rank,
null, // extras
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 16191687..be8eeed 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1510,6 +1510,10 @@
ps.clearAllImplicitRanks();
assignImplicitRanks(newShortcuts);
+ for (int i = 0; i < size; i++) {
+ fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false);
+ }
+
// First, remove all un-pinned; dynamic shortcuts
ps.deleteAllDynamicShortcuts();
diff --git a/services/tests/servicestests/res/drawable/icon3.png b/services/tests/servicestests/res/drawable/icon3.png
new file mode 100644
index 0000000..64eb294
--- /dev/null
+++ b/services/tests/servicestests/res/drawable/icon3.png
Binary files differ
diff --git a/services/tests/servicestests/res/xml/shortcut_1.xml b/services/tests/servicestests/res/xml/shortcut_1.xml
index f6d54fc..e3f9172 100644
--- a/services/tests/servicestests/res/xml/shortcut_1.xml
+++ b/services/tests/servicestests/res/xml/shortcut_1.xml
@@ -2,12 +2,17 @@
<shortcut
android:shortcutId="ms1"
android:enabled="true"
- android:shortcutIcon="@drawable/icon1"
+ android:icon="@drawable/icon1"
android:shortcutShortLabel="@string/shortcut_title1"
android:shortcutLongLabel="@string/shortcut_text1"
android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
- android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
- android:shortcutIntentAction="action1"
- android:shortcutIntentData="data1"
- />
+ >
+ <intent
+ android:action="action1"
+ android:data="data1"
+ >
+ </intent>
+ <categories android:name="android.shortcut.conversation" />
+ <categories android:name="android.shortcut.media" />
+ </shortcut>
</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_1_alt.xml b/services/tests/servicestests/res/xml/shortcut_1_alt.xml
index bf14f49..2d5e8e7 100644
--- a/services/tests/servicestests/res/xml/shortcut_1_alt.xml
+++ b/services/tests/servicestests/res/xml/shortcut_1_alt.xml
@@ -17,12 +17,17 @@
<shortcut
android:shortcutId="ms1-alt"
android:enabled="true"
- android:shortcutIcon="@drawable/icon1"
+ android:icon="@drawable/icon1"
android:shortcutShortLabel="@string/shortcut_title1"
android:shortcutLongLabel="@string/shortcut_text1"
android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
- android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
- android:shortcutIntentAction="action1"
- android:shortcutIntentData="data1"
- />
+ >
+ <intent
+ android:action="action1"
+ android:data="data1"
+ >
+ </intent>
+ <categories android:name="android.shortcut.conversation" />
+ <categories android:name="android.shortcut.media" />
+ </shortcut>
</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_1_disable.xml b/services/tests/servicestests/res/xml/shortcut_1_disable.xml
index 81a84b4..e3ee3a0 100644
--- a/services/tests/servicestests/res/xml/shortcut_1_disable.xml
+++ b/services/tests/servicestests/res/xml/shortcut_1_disable.xml
@@ -17,7 +17,7 @@
<shortcut
android:shortcutId="ms1"
android:enabled="false"
- android:shortcutIcon="@drawable/icon2"
+ android:icon="@drawable/icon2"
android:shortcutShortLabel="@string/shortcut_title2"
android:shortcutLongLabel="@string/shortcut_text2"
android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
diff --git a/services/tests/servicestests/res/xml/shortcut_2.xml b/services/tests/servicestests/res/xml/shortcut_2.xml
index 96ed382..f7ea803 100644
--- a/services/tests/servicestests/res/xml/shortcut_2.xml
+++ b/services/tests/servicestests/res/xml/shortcut_2.xml
@@ -17,23 +17,32 @@
<shortcut
android:shortcutId="ms1"
android:enabled="true"
- android:shortcutIcon="@drawable/icon1"
+ android:icon="@drawable/icon1"
android:shortcutShortLabel="@string/shortcut_title1"
android:shortcutLongLabel="@string/shortcut_text1"
android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
- android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
- android:shortcutIntentAction="action1"
- android:shortcutIntentData="http://a.b.c/"
- />
+ >
+ <intent
+ android:action="action1"
+ android:data="http://a.b.c/"
+ >
+ </intent>
+ <categories android:name="android.shortcut.conversation" />
+ <categories android:name="android.shortcut.media" />
+ </shortcut>
<shortcut
android:shortcutId="ms2"
android:enabled="true"
- android:shortcutIcon="@drawable/icon2"
+ android:icon="@drawable/icon2"
android:shortcutShortLabel="@string/shortcut_title2"
android:shortcutLongLabel="@string/shortcut_text2"
android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
- android:shortcutCategories="android.shortcut.conversation"
- android:shortcutIntentAction="action2"
- android:shortcutIntentData="http://a.b.c/2"
- />
+ >
+ <intent
+ android:action="action2"
+ android:data="http://a.b.c/2"
+ >
+ </intent>
+ <categories android:name="android.shortcut.conversation" />
+ </shortcut>
</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_2_duplicate.xml b/services/tests/servicestests/res/xml/shortcut_2_duplicate.xml
index 2f814b7..b00ec60 100644
--- a/services/tests/servicestests/res/xml/shortcut_2_duplicate.xml
+++ b/services/tests/servicestests/res/xml/shortcut_2_duplicate.xml
@@ -17,11 +17,19 @@
<shortcut
android:shortcutId="ms1"
android:shortcutShortLabel="@string/shortcut_title1"
- android:shortcutIntentAction="action1"
- />
+ >
+ <intent
+ android:action="action1"
+ >
+ </intent>
+ </shortcut>
<shortcut
android:shortcutId="ms1"
android:shortcutShortLabel="@string/shortcut_title2"
- android:shortcutIntentAction="action2"
- />
+ >
+ <intent
+ android:action="action2"
+ >
+ </intent>
+ </shortcut>
</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_5.xml b/services/tests/servicestests/res/xml/shortcut_5.xml
index 56dba0e..9551100 100644
--- a/services/tests/servicestests/res/xml/shortcut_5.xml
+++ b/services/tests/servicestests/res/xml/shortcut_5.xml
@@ -17,37 +17,69 @@
<shortcut
android:shortcutId="ms1"
android:enabled="true"
- android:shortcutIcon="@drawable/icon1"
+ android:icon="@drawable/icon1"
android:shortcutShortLabel="@string/shortcut_title1"
android:shortcutLongLabel="@string/shortcut_text1"
android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
- android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
- android:shortcutIntentAction="action1"
- android:shortcutIntentData="http://a.b.c/1"
- />
+ >
+ <intent
+ android:action="action1"
+ android:data="http://a.b.c/1"
+ >
+ </intent>
+ <categories android:name="android.shortcut.conversation" />
+ <categories android:name="android.shortcut.media" />
+ </shortcut>
<shortcut
android:shortcutId="ms2"
android:enabled="true"
- android:shortcutIcon="@drawable/icon2"
+ android:icon="@drawable/icon2"
android:shortcutShortLabel="@string/shortcut_title2"
android:shortcutLongLabel="@string/shortcut_text2"
android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
- android:shortcutCategories="android.shortcut.conversation"
- android:shortcutIntentAction="action2"
- />
+ >
+ <intent
+ android:action="action2"
+ >
+ </intent>
+ <categories android:name="android.shortcut.conversation" />
+ </shortcut>
<shortcut
android:shortcutId="ms3"
android:shortcutShortLabel="@string/shortcut_title1"
- android:shortcutIntentAction="android.intent.action.VIEW"
- />
+ >
+ <intent
+ android:action="android.intent.action.VIEW"
+ >
+ </intent>
+ </shortcut>
<shortcut
android:shortcutId="ms4"
- android:shortcutShortLabel="@string/shortcut_title1"
- android:shortcutIntentAction="android.intent.action.VIEW"
- />
+ android:shortcutShortLabel="@string/shortcut_title2"
+ >
+ <intent
+ android:action="android.intent.action.VIEW2"
+ >
+ </intent>
+ <categories />
+ <categories android:name="" />
+ <categories android:name="cat" />
+ </shortcut>
<shortcut
android:shortcutId="ms5"
android:shortcutShortLabel="@string/shortcut_title1"
- android:shortcutIntentAction="android.intent.action.VIEW"
- />
+ >
+ <intent
+ android:action="action"
+ android:data="http://www/"
+ android:targetPackage="abc"
+ android:targetClass=".xyz"
+ android:mimeType="foo/bar"
+ >
+ <categories android:name="cat1" />
+ <categories android:name="cat2" />
+ <extra android:name="key1" android:value="value1" />
+ <extra android:name="key2" android:value="value2" />
+ </intent>
+ </shortcut>
</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_5_alt.xml b/services/tests/servicestests/res/xml/shortcut_5_alt.xml
index 74085d9..f79cd6f 100644
--- a/services/tests/servicestests/res/xml/shortcut_5_alt.xml
+++ b/services/tests/servicestests/res/xml/shortcut_5_alt.xml
@@ -17,37 +17,58 @@
<shortcut
android:shortcutId="ms1_alt"
android:enabled="true"
- android:shortcutIcon="@drawable/icon1"
+ android:icon="@drawable/icon1"
android:shortcutShortLabel="@string/shortcut_title1"
android:shortcutLongLabel="@string/shortcut_text1"
android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
- android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
- android:shortcutIntentAction="action1"
- android:shortcutIntentData="http://a.b.c/1"
- />
+ >
+ <intent
+ android:action="action1"
+ android:data="http://a.b.c/1"
+ >
+ </intent>
+ <categories android:name="android.shortcut.conversation" />
+ <categories android:name="android.shortcut.media" />
+ </shortcut>
<shortcut
android:shortcutId="ms2_alt"
android:enabled="true"
- android:shortcutIcon="@drawable/icon2"
+ android:icon="@drawable/icon2"
android:shortcutShortLabel="@string/shortcut_title2"
android:shortcutLongLabel="@string/shortcut_text2"
android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
- android:shortcutCategories="android.shortcut.conversation"
- android:shortcutIntentAction="action2"
- />
+ >
+ <intent
+ android:action="action2"
+ >
+ </intent>
+ <categories android:name="android.shortcut.conversation" />
+ </shortcut>
<shortcut
android:shortcutId="ms3_alt"
android:shortcutShortLabel="@string/shortcut_title1"
- android:shortcutIntentAction="android.intent.action.VIEW"
- />
+ >
+ <intent
+ android:action="android.intent.action.VIEW"
+ >
+ </intent>
+ </shortcut>
<shortcut
android:shortcutId="ms4_alt"
android:shortcutShortLabel="@string/shortcut_title1"
- android:shortcutIntentAction="android.intent.action.VIEW"
- />
+ >
+ <intent
+ android:action="android.intent.action.VIEW"
+ >
+ </intent>
+ </shortcut>
<shortcut
android:shortcutId="ms5_alt"
android:shortcutShortLabel="@string/shortcut_title1"
- android:shortcutIntentAction="android.intent.action.VIEW"
- />
+ >
+ <intent
+ android:action="android.intent.action.VIEW"
+ >
+ </intent>
+ </shortcut>
</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_5_reverse.xml b/services/tests/servicestests/res/xml/shortcut_5_reverse.xml
index 3d6eb22..d5b7c8f 100644
--- a/services/tests/servicestests/res/xml/shortcut_5_reverse.xml
+++ b/services/tests/servicestests/res/xml/shortcut_5_reverse.xml
@@ -17,37 +17,62 @@
<shortcut
android:shortcutId="ms5"
android:enabled="true"
- android:shortcutIcon="@drawable/icon1"
+ android:icon="@drawable/icon1"
android:shortcutShortLabel="@string/shortcut_title1"
android:shortcutLongLabel="@string/shortcut_text1"
android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
- android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
- android:shortcutIntentAction="action1"
- android:shortcutIntentData="http://a.b.c/1"
- />
+ >
+ <intent
+ android:action="action1"
+ android:data="http://a.b.c/1"
+ >
+ </intent>
+ <categories android:name="android.shortcut.conversation" />
+ <categories android:name="android.shortcut.media" />
+ </shortcut>
<shortcut
android:shortcutId="ms4"
android:enabled="true"
- android:shortcutIcon="@drawable/icon2"
+ android:icon="@drawable/icon2"
android:shortcutShortLabel="@string/shortcut_title2"
android:shortcutLongLabel="@string/shortcut_text2"
android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
- android:shortcutCategories="android.shortcut.conversation"
- android:shortcutIntentAction="action2"
- />
+ >
+ <intent
+ android:action="action2"
+ >
+ </intent>
+ <categories android:name="android.shortcut.conversation" />
+ </shortcut>
<shortcut
android:shortcutId="ms3"
android:shortcutShortLabel="@string/shortcut_title1"
- android:shortcutIntentAction="android.intent.action.VIEW"
- />
+ >
+ <intent
+ android:action="android.intent.action.VIEW"
+ >
+ </intent>
+ </shortcut>
<shortcut
android:shortcutId="ms2"
android:shortcutShortLabel="@string/shortcut_title1"
- android:shortcutIntentAction="android.intent.action.VIEW"
- />
+ >
+ <intent
+ android:action="android.intent.action.VIEW"
+ >
+ </intent>
+ </shortcut>
<shortcut
android:shortcutId="ms1"
android:shortcutShortLabel="@string/shortcut_title1"
- android:shortcutIntentAction="android.intent.action.VIEW"
- />
+ >
+ <intent
+ android:action="action"
+ >
+ <categories android:name="cat1" />
+ <categories android:name="cat2" />
+ <extra android:name="key1" android:value="value1" />
+ <extra android:name="key2" android:value="value2" />
+ </intent>
+ </shortcut>
</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_error_1.xml b/services/tests/servicestests/res/xml/shortcut_error_1.xml
index 5822496..3990d02 100644
--- a/services/tests/servicestests/res/xml/shortcut_error_1.xml
+++ b/services/tests/servicestests/res/xml/shortcut_error_1.xml
@@ -16,11 +16,19 @@
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
<shortcut
android:shortcutShortLabel="@string/shortcut_title1"
- android:shortcutIntentAction="android.intent.action.VIEW"
- />
+ >
+ <intent
+ android:action="android.intent.action.VIEW"
+ >
+ </intent>
+ </shortcut>
<shortcut
android:shortcutId="x1"
android:shortcutShortLabel="@string/shortcut_title1"
- android:shortcutIntentAction="android.intent.action.VIEW"
- />
+ >
+ <intent
+ android:action="android.intent.action.VIEW"
+ >
+ </intent>
+ </shortcut>
</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_error_2.xml b/services/tests/servicestests/res/xml/shortcut_error_2.xml
index ca67ec7..a6f7150 100644
--- a/services/tests/servicestests/res/xml/shortcut_error_2.xml
+++ b/services/tests/servicestests/res/xml/shortcut_error_2.xml
@@ -16,11 +16,19 @@
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
<shortcut
android:shortcutId="manifest-shortcut-3"
- android:shortcutIntentAction="android.intent.action.VIEW"
- />
+ >
+ <intent
+ android:action="android.intent.action.VIEW"
+ >
+ </intent>
+ </shortcut>
<shortcut
android:shortcutId="x2"
android:shortcutShortLabel="@string/shortcut_title1"
- android:shortcutIntentAction="android.intent.action.VIEW"
- />
+ >
+ <intent
+ android:action="android.intent.action.VIEW"
+ >
+ </intent>
+ </shortcut>
</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_error_3.xml b/services/tests/servicestests/res/xml/shortcut_error_3.xml
index fb7b31c..a7b9b84 100644
--- a/services/tests/servicestests/res/xml/shortcut_error_3.xml
+++ b/services/tests/servicestests/res/xml/shortcut_error_3.xml
@@ -21,6 +21,10 @@
<shortcut
android:shortcutId="x3"
android:shortcutShortLabel="@string/shortcut_title1"
- android:shortcutIntentAction="android.intent.action.VIEW"
- />
+ >
+ <intent
+ android:action="android.intent.action.VIEW"
+ >
+ </intent>
+ </shortcut>
</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_error_4.xml b/services/tests/servicestests/res/xml/shortcut_error_4.xml
new file mode 100644
index 0000000..3697bb4
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_error_4.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <!-- This is valid -->
+ <shortcut
+ android:shortcutId="ms1"
+ android:shortcutShortLabel="@string/shortcut_title1"
+ >
+ <intent android:action="action1" />
+ </shortcut>
+
+ <!-- Invalid: no intent -->
+ <shortcut
+ android:shortcutId="ms_ignored1"
+ android:shortcutShortLabel="@string/shortcut_title1"
+ />
+
+ <!-- Valid: more than one intent; first one will be picked. -->
+ <shortcut
+ android:shortcutId="ms2"
+ android:shortcutShortLabel="@string/shortcut_title1"
+ >
+ <intent android:action="action2_1" />
+ <intent android:action="action2_2" />
+ </shortcut>
+
+ <!-- Valid: disabled shortcut doesn't need an intent -->
+ <shortcut
+ android:shortcutId="ms3"
+ android:enabled="false"
+ android:shortcutShortLabel="@string/shortcut_title1"
+ />
+
+ <!-- Valid, but disabled shortcut's intent will be ignored. -->
+ <shortcut
+ android:shortcutId="ms4"
+ android:enabled="false"
+ android:shortcutShortLabel="@string/shortcut_title1"
+ >
+ <intent android:action="action4" />
+ </shortcut>
+
+ <!-- Invalid, no intent action -->
+ <shortcut
+ android:shortcutId="ms_ignored2"
+ android:shortcutShortLabel="@string/shortcut_title1"
+ >
+ <intent android:data="x"/>
+ </shortcut>
+</shortcuts>
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 de06047..56232c0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -4884,6 +4884,117 @@
});
}
+ public void testManifestShortcuts_intentDefinitions() {
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_error_4);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ // Make sure invalid ones are not published.
+ // Note that at this point disabled ones don't show up because they weren't pinned.
+ assertWith(getCallerShortcuts())
+ .haveIds("ms1", "ms2")
+ .areAllManifest()
+ .areAllNotDynamic()
+ .areAllNotPinned()
+ .areAllImmutable()
+ .areAllEnabled()
+ .forShortcutWithId("ms1", si -> {
+ assertTrue(si.isEnabled());
+ assertEquals("action1", si.getIntent().getAction());
+ })
+ .forShortcutWithId("ms2", si -> {
+ assertTrue(si.isEnabled());
+ assertEquals("action2_1", si.getIntent().getAction());
+ });
+ });
+
+ // Publish 5 enabled to pin some, so we can later test disabled manfiest shortcuts..
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_5);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ // Make sure 5 manifest shortcuts are published.
+ assertWith(getCallerShortcuts())
+ .haveIds("ms1", "ms2", "ms3", "ms4", "ms5")
+ .areAllManifest()
+ .areAllNotDynamic()
+ .areAllNotPinned()
+ .areAllImmutable()
+ .areAllEnabled();
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+ list("ms3", "ms4", "ms5"), HANDLE_USER_0);
+ });
+
+ // Make sure they're pinned.
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertWith(getCallerShortcuts())
+ .haveIds("ms1", "ms2", "ms3", "ms4", "ms5")
+ .selectByIds("ms1", "ms2")
+ .areAllNotPinned()
+ .areAllEnabled()
+
+ .revertToOriginalList()
+ .selectByIds("ms3", "ms4", "ms5")
+ .areAllPinned()
+ .areAllEnabled();
+ });
+
+ // Update the app.
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_error_4);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+ // Make sure 3, 4 and 5 still exist but disabled.
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertWith(getCallerShortcuts())
+ .haveIds("ms1", "ms2", "ms3", "ms4", "ms5")
+ .areAllNotDynamic()
+ .areAllImmutable()
+
+ .selectByIds("ms1", "ms2")
+ .areAllManifest()
+ .areAllNotPinned()
+ .areAllEnabled()
+
+ .revertToOriginalList()
+ .selectByIds("ms3", "ms4", "ms5")
+ .areAllNotManifest()
+ .areAllPinned()
+ .areAllDisabled()
+
+ .revertToOriginalList()
+ .forShortcutWithId("ms1", si -> {
+ assertEquals(si.getId(), "action1", si.getIntent().getAction());
+ })
+ .forShortcutWithId("ms2", si -> {
+ assertEquals(si.getId(), "action2_1", si.getIntent().getAction());
+ })
+ .forShortcutWithId("ms3", si -> {
+ assertEquals(si.getId(), Intent.ACTION_VIEW, si.getIntent().getAction());
+ })
+ .forShortcutWithId("ms4", si -> {
+ assertEquals(si.getId(), Intent.ACTION_VIEW, si.getIntent().getAction());
+ })
+ .forShortcutWithId("ms5", si -> {
+ assertEquals(si.getId(), "action", si.getIntent().getAction());
+ });
+ });
+ }
+
public void testManifestShortcuts_checkAllFields() {
mService.handleUnlockUser(USER_0);
@@ -4897,63 +5008,95 @@
// Only the valid one is published.
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
- mManager.getManifestShortcuts()))),
- "ms1", "ms2", "ms3", "ms4", "ms5");
+ assertWith(getCallerShortcuts())
+ .haveIds("ms1", "ms2", "ms3", "ms4", "ms5")
+ .areAllManifest()
+ .areAllImmutable()
+ .areAllEnabled()
+ .areAllNotPinned()
+ .areAllNotDynamic()
- // check first shortcut.
- ShortcutInfo si = getCallerShortcut("ms1");
+ .forShortcutWithId("ms1", si -> {
+ assertEquals(R.drawable.icon1, si.getIconResourceId());
+ assertEquals(new ComponentName(CALLING_PACKAGE_1,
+ ShortcutActivity.class.getName()),
+ si.getActivity());
- assertEquals("ms1", si.getId());
- assertEquals(R.drawable.icon1, si.getIconResourceId());
- assertEquals(new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
- si.getActivity());
+ assertEquals(R.string.shortcut_title1, si.getTitleResId());
+ assertEquals("r" + R.string.shortcut_title1, si.getTitleResName());
+ assertEquals(R.string.shortcut_text1, si.getTextResId());
+ assertEquals("r" + R.string.shortcut_text1, si.getTextResName());
+ assertEquals(R.string.shortcut_disabled_message1,
+ si.getDisabledMessageResourceId());
+ assertEquals("r" + R.string.shortcut_disabled_message1,
+ si.getDisabledMessageResName());
- assertEquals(R.string.shortcut_title1, si.getTitleResId());
- assertEquals("r" + R.string.shortcut_title1, si.getTitleResName());
- assertEquals(R.string.shortcut_text1, si.getTextResId());
- assertEquals("r" + R.string.shortcut_text1, si.getTextResName());
- assertEquals(R.string.shortcut_disabled_message1, si.getDisabledMessageResourceId());
- assertEquals("r" + R.string.shortcut_disabled_message1, si.getDisabledMessageResName());
+ assertEquals(set("android.shortcut.conversation", "android.shortcut.media"),
+ si.getCategories());
+ assertEquals("action1", si.getIntent().getAction());
+ assertEquals(Uri.parse("http://a.b.c/1"), si.getIntent().getData());
+ })
- assertEquals(set("android.shortcut.conversation", "android.shortcut.media"),
- si.getCategories());
- assertEquals("action1", si.getIntent().getAction());
- assertEquals(Uri.parse("http://a.b.c/1"), si.getIntent().getData());
+ .forShortcutWithId("ms2", si -> {
+ assertEquals("ms2", si.getId());
+ assertEquals(R.drawable.icon2, si.getIconResourceId());
- // check another
- si = getCallerShortcut("ms2");
+ assertEquals(R.string.shortcut_title2, si.getTitleResId());
+ assertEquals("r" + R.string.shortcut_title2, si.getTitleResName());
+ assertEquals(R.string.shortcut_text2, si.getTextResId());
+ assertEquals("r" + R.string.shortcut_text2, si.getTextResName());
+ assertEquals(R.string.shortcut_disabled_message2,
+ si.getDisabledMessageResourceId());
+ assertEquals("r" + R.string.shortcut_disabled_message2,
+ si.getDisabledMessageResName());
- assertEquals("ms2", si.getId());
- assertEquals(R.drawable.icon2, si.getIconResourceId());
+ assertEquals(set("android.shortcut.conversation"), si.getCategories());
+ assertEquals("action2", si.getIntent().getAction());
+ assertEquals(null, si.getIntent().getData());
+ })
- assertEquals(R.string.shortcut_title2, si.getTitleResId());
- assertEquals("r" + R.string.shortcut_title2, si.getTitleResName());
- assertEquals(R.string.shortcut_text2, si.getTextResId());
- assertEquals("r" + R.string.shortcut_text2, si.getTextResName());
- assertEquals(R.string.shortcut_disabled_message2, si.getDisabledMessageResourceId());
- assertEquals("r" + R.string.shortcut_disabled_message2, si.getDisabledMessageResName());
+ .forShortcutWithId("ms3", si -> {
+ assertEquals(0, si.getIconResourceId());
+ assertEquals(R.string.shortcut_title1, si.getTitleResId());
+ assertEquals("r" + R.string.shortcut_title1, si.getTitleResName());
- assertEquals(set("android.shortcut.conversation"), si.getCategories());
- assertEquals("action2", si.getIntent().getAction());
- assertEquals(null, si.getIntent().getData());
+ assertEquals(0, si.getTextResId());
+ assertEquals(null, si.getTextResName());
+ assertEquals(0, si.getDisabledMessageResourceId());
+ assertEquals(null, si.getDisabledMessageResName());
- // check another
- si = getCallerShortcut("ms3");
+ assertEmpty(si.getCategories());
+ assertEquals("android.intent.action.VIEW", si.getIntent().getAction());
+ assertEquals(null, si.getIntent().getData());
+ })
- assertEquals("ms3", si.getId());
- assertEquals(0, si.getIconResourceId());
- assertEquals(R.string.shortcut_title1, si.getTitleResId());
- assertEquals("r" + R.string.shortcut_title1, si.getTitleResName());
+ .forShortcutWithId("ms4", si -> {
+ assertEquals(0, si.getIconResourceId());
+ assertEquals(R.string.shortcut_title2, si.getTitleResId());
+ assertEquals("r" + R.string.shortcut_title2, si.getTitleResName());
- assertEquals(0, si.getTextResId());
- assertEquals(null, si.getTextResName());
- assertEquals(0, si.getDisabledMessageResourceId());
- assertEquals(null, si.getDisabledMessageResName());
+ assertEquals(0, si.getTextResId());
+ assertEquals(null, si.getTextResName());
+ assertEquals(0, si.getDisabledMessageResourceId());
+ assertEquals(null, si.getDisabledMessageResName());
- assertEquals(null, si.getCategories());
- assertEquals("android.intent.action.VIEW", si.getIntent().getAction());
- assertEquals(null, si.getIntent().getData());
+ assertEquals(set("cat"), si.getCategories());
+ assertEquals("android.intent.action.VIEW2", si.getIntent().getAction());
+ assertEquals(null, si.getIntent().getData());
+ })
+
+ .forShortcutWithId("ms5", si -> {
+ si = getCallerShortcut("ms5");
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("http://www/", si.getIntent().getData().toString());
+ assertEquals("foo/bar", si.getIntent().getType());
+ assertEquals(
+ new ComponentName("abc", ".xyz"), si.getIntent().getComponent());
+
+ assertEquals(set("cat1", "cat2"), si.getIntent().getCategories());
+ assertEquals("value1", si.getIntent().getStringExtra("key1"));
+ assertEquals("value2", si.getIntent().getStringExtra("key2"));
+ });
});
}
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 7ba4c68..712bc1e 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
@@ -43,9 +43,11 @@
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.test.MoreAsserts;
+import android.util.ArraySet;
import android.util.Log;
import junit.framework.Assert;
+import junit.framework.AssertionFailedError;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
@@ -66,6 +68,7 @@
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -259,9 +262,12 @@
}
}
- public static <T> List<T> assertEmpty(List<T> list) {
- assertEquals(0, list.size());
- return list;
+ public static <T extends Collection<?>> T assertEmpty(T collection) {
+ if (collection == null) {
+ return collection; // okay.
+ }
+ assertEquals(0, collection.size());
+ return collection;
}
public static List<ShortcutInfo> filter(List<ShortcutInfo> list, Predicate<ShortcutInfo> p) {
@@ -653,42 +659,73 @@
* New style assertion that allows chained calls.
*/
public static class ShortcutListAsserter {
+ private final ShortcutListAsserter mOriginal;
private final List<ShortcutInfo> mList;
ShortcutListAsserter(List<ShortcutInfo> list) {
+ this(null, list);
+ }
+
+ private ShortcutListAsserter(ShortcutListAsserter original, List<ShortcutInfo> list) {
+ mOriginal = original == null ? this : original;
mList = new ArrayList<>(list);
}
+ public ShortcutListAsserter revertToOriginalList() {
+ return mOriginal;
+ }
+
public ShortcutListAsserter selectDynamic() {
- return new ShortcutListAsserter(
+ return new ShortcutListAsserter(this,
filter(mList, ShortcutInfo::isDynamic));
}
public ShortcutListAsserter selectManifest() {
- return new ShortcutListAsserter(
+ return new ShortcutListAsserter(this,
filter(mList, ShortcutInfo::isManifestShortcut));
}
public ShortcutListAsserter selectPinned() {
- return new ShortcutListAsserter(
+ return new ShortcutListAsserter(this,
filter(mList, ShortcutInfo::isPinned));
}
public ShortcutListAsserter selectByActivity(ComponentName activity) {
- return new ShortcutListAsserter(
+ return new ShortcutListAsserter(this,
ShortcutManagerTestUtils.filterByActivity(mList, activity));
}
public ShortcutListAsserter selectByChangedSince(long time) {
- return new ShortcutListAsserter(
+ return new ShortcutListAsserter(this,
ShortcutManagerTestUtils.changedSince(mList, time));
}
+ public ShortcutListAsserter selectByIds(String... ids) {
+ final Set<String> idSet = set(ids);
+ final ArrayList<ShortcutInfo> selected = new ArrayList<>();
+ for (ShortcutInfo si : mList) {
+ if (idSet.contains(si.getId())) {
+ selected.add(si);
+ idSet.remove(si.getId());
+ }
+ }
+ if (idSet.size() > 0) {
+ fail("Shortcuts not found for IDs=" + idSet);
+ }
+
+ return new ShortcutListAsserter(this, selected);
+ }
+
public ShortcutListAsserter toSortByRank() {
- return new ShortcutListAsserter(
+ return new ShortcutListAsserter(this,
ShortcutManagerTestUtils.sortedByRank(mList));
}
+ public ShortcutListAsserter call(Consumer<List<ShortcutInfo>> c) {
+ c.accept(mList);
+ return this;
+ }
+
public ShortcutListAsserter haveIds(String... expectedIds) {
assertShortcutIds(mList, expectedIds);
return this;
@@ -701,7 +738,8 @@
private ShortcutListAsserter haveSequentialRanks() {
for (int i = 0; i < mList.size(); i++) {
- assertEquals("Rank not sequential", i, mList.get(i).getRank());
+ final ShortcutInfo si = mList.get(i);
+ assertEquals("Rank not sequential: id=" + si.getId(), i, si.getRank());
}
return this;
}
@@ -717,5 +755,87 @@
assertEquals(0, mList.size());
return this;
}
+
+ public ShortcutListAsserter areAllDynamic() {
+ forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isDynamic()));
+ return this;
+ }
+
+ public ShortcutListAsserter areAllNotDynamic() {
+ forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isDynamic()));
+ return this;
+ }
+
+ public ShortcutListAsserter areAllPinned() {
+ forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isPinned()));
+ return this;
+ }
+
+ public ShortcutListAsserter areAllNotPinned() {
+ forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isPinned()));
+ return this;
+ }
+
+ public ShortcutListAsserter areAllManifest() {
+ forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isManifestShortcut()));
+ return this;
+ }
+
+ public ShortcutListAsserter areAllNotManifest() {
+ forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isManifestShortcut()));
+ return this;
+ }
+
+ public ShortcutListAsserter areAllImmutable() {
+ forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isImmutable()));
+ return this;
+ }
+
+ public ShortcutListAsserter areAllMutable() {
+ forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isImmutable()));
+ return this;
+ }
+
+ public ShortcutListAsserter areAllEnabled() {
+ forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isEnabled()));
+ return this;
+ }
+
+ public ShortcutListAsserter areAllDisabled() {
+ forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isEnabled()));
+ return this;
+ }
+
+ public ShortcutListAsserter forAllShortcuts(Consumer<ShortcutInfo> sa) {
+ for (int i = 0; i < mList.size(); i++) {
+ final ShortcutInfo si = mList.get(i);
+ sa.accept(si);
+ }
+ return this;
+ }
+
+ public ShortcutListAsserter forShortcut(Predicate<ShortcutInfo> p,
+ Consumer<ShortcutInfo> sa) {
+ boolean found = false;
+ for (int i = 0; i < mList.size(); i++) {
+ final ShortcutInfo si = mList.get(i);
+ if (p.test(si)) {
+ found = true;
+ try {
+ sa.accept(si);
+ } catch (Throwable e) {
+ throw new AssertionError("Assertion failed for shortcut " + si.getId(), e);
+ }
+ }
+ }
+ assertTrue("Shortcut with the given condition not found.", found);
+ return this;
+ }
+
+ public ShortcutListAsserter forShortcutWithId(String id, Consumer<ShortcutInfo> sa) {
+ forShortcut(si -> si.getId().equals(id), sa);
+
+ return this;
+ }
}
}