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
This commit is contained in:
Makoto Onuki
2016-06-16 09:51:40 -07:00
parent 939f8caec2
commit df6da040e0
24 changed files with 810 additions and 256 deletions

View File

@@ -420,9 +420,9 @@ package android {
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 @@ package android {
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 @@ package android {
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 @@ package android.content.pm {
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 {

View File

@@ -527,9 +527,9 @@ package android {
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 @@ package android {
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 @@ package android {
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 @@ package android.content.pm {
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 {

View File

@@ -420,9 +420,9 @@ package android {
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 @@ package android {
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 @@ package android {
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 @@ package android.content.pm {
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 {

View File

@@ -30,6 +30,7 @@ import android.os.Parcel;
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 @@ public final class ShortcutInfo implements Parcelable {
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 @@ public final class ShortcutInfo implements Parcelable {
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 @@ public final class ShortcutInfo implements Parcelable {
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 @@ public final class ShortcutInfo implements Parcelable {
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 @@ public final class ShortcutInfo implements Parcelable {
* 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 @@ public final class ShortcutInfo implements Parcelable {
* 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 @@ public final class ShortcutInfo implements Parcelable {
}
/** @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 @@ public final class ShortcutInfo implements Parcelable {
}
@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 @@ public final class ShortcutInfo implements Parcelable {
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 @@ public final class ShortcutInfo implements Parcelable {
mDisabledMessage = disabledMessage;
mDisabledMessageResId = disabledMessageResId;
mDisabledMessageResName = disabledMessageResName;
mCategories = clone(categories);
mCategories = cloneCategories(categories);
mIntent = intent;
mIntentPersistableExtras = intentPersistableExtras;
mRank = rank;

View File

@@ -8251,12 +8251,13 @@ i
<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>

View File

@@ -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" />

View File

@@ -682,6 +682,8 @@ class ShortcutPackage extends ShortcutPackageItem {
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 @@ class ShortcutPackage extends ShortcutPackageItem {
}
removeOrphans();
}
adjustRanks();
return changed;
}
@@ -810,7 +813,6 @@ class ShortcutPackage extends ShortcutPackageItem {
deleteDynamicWithId(shortcut.getId());
}
}
service.verifyStates();
return changed;
}

View File

@@ -24,10 +24,10 @@ import android.content.pm.PackageInfo;
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 @@ public class ShortcutParser {
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 @@ public class ShortcutParser {
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 @@ public class ShortcutParser {
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 @@ public class ShortcutParser {
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 @@ public class ShortcutParser {
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 @@ public class ShortcutParser {
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 @@ public class ShortcutParser {
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 @@ public class ShortcutParser {
titleResId,
textResId,
disabledMessageResId,
categoriesSet,
intent,
rank,
iconResId,
enabled);
@@ -214,8 +285,8 @@ public class ShortcutParser {
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 @@ public class ShortcutParser {
null, // disabled message string
disabledMessageResId,
null, // disabled message res name
categories,
intent,
null, // categories
null, // intent
null, // intent extras
rank,
null, // extras

View File

@@ -1510,6 +1510,10 @@ public class ShortcutService extends IShortcutService.Stub {
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();

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -4884,6 +4884,117 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// 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"));
});
});
}

View File

@@ -43,9 +43,11 @@ import android.os.Parcel;
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.Set;
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 class ShortcutManagerTestUtils {
}
}
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 @@ public class ShortcutManagerTestUtils {
* 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 @@ public class ShortcutManagerTestUtils {
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 @@ public class ShortcutManagerTestUtils {
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;
}
}
}