ShortcutManager: Support manifest shortcuts
- Now apps can publish shortcuts via AndroidManifest.xml
(Sample: I5b127150)
- Rename some APIs per API council feedback
- Publishers can now "disable" shortcuts.
(https://docs.google.com/presentation/d/1raUn1QBURDb1yrd6mSmVxII9ezNh3MWukODdtufJ29U/edit#slide=id.g13ef592464_7_39)
Bug 28785283
Bug 28536066
Change-Id: I4a126841e43e40139bb4baa6d0f98ad7b3a75ac1
diff --git a/api/current.txt b/api/current.txt
index 427d9b3..114ef0d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1106,6 +1106,15 @@
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 = 16844078; // 0x101052e
+ field public static final int shortcutDisabledMessage = 16844077; // 0x101052d
+ field public static final int shortcutIcon = 16844074; // 0x101052a
+ field public static final int shortcutId = 16844072; // 0x1010528
+ field public static final int shortcutIntentAction = 16844079; // 0x101052f
+ field public static final int shortcutIntentData = 16844080; // 0x1010530
+ field public static final int shortcutRank = 16844073; // 0x1010529
+ field public static final int shortcutText = 16844076; // 0x101052c
+ field public static final int shortcutTitle = 16844075; // 0x101052b
field public static final int shouldDisableView = 16843246; // 0x10101ee
field public static final int showAsAction = 16843481; // 0x10102d9
field public static final int showDefault = 16843258; // 0x10101fa
@@ -9534,13 +9543,14 @@
public static class LauncherApps.ShortcutQuery {
ctor public LauncherApps.ShortcutQuery();
- method public void setActivity(android.content.ComponentName);
- method public void setChangedSince(long);
- method public void setPackage(java.lang.String);
- method public void setQueryFlags(int);
- method public void setShortcutIds(java.util.List<java.lang.String>);
+ method public android.content.pm.LauncherApps.ShortcutQuery setActivity(android.content.ComponentName);
+ method public android.content.pm.LauncherApps.ShortcutQuery setChangedSince(long);
+ method public android.content.pm.LauncherApps.ShortcutQuery setPackage(java.lang.String);
+ method public android.content.pm.LauncherApps.ShortcutQuery setQueryFlags(int);
+ method public android.content.pm.LauncherApps.ShortcutQuery setShortcutIds(java.util.List<java.lang.String>);
field public static final int FLAG_GET_DYNAMIC = 1; // 0x1
field public static final int FLAG_GET_KEY_FIELDS_ONLY = 4; // 0x4
+ field public static final int FLAG_GET_MANIFEST = 8; // 0x8
field public static final int FLAG_GET_PINNED = 2; // 0x2
}
@@ -10044,29 +10054,30 @@
public final class ShortcutInfo implements android.os.Parcelable {
method public int describeContents();
- method public android.content.ComponentName getActivityComponent();
+ method public android.content.ComponentName getActivity();
method public java.util.Set<java.lang.String> getCategories();
- method public java.lang.String getDisabledMessage();
+ method public java.lang.CharSequence getDisabledMessage();
method public int getDisabledMessageResId();
method public android.os.PersistableBundle getExtras();
method public int getIconResourceId();
method public java.lang.String getId();
method public android.content.Intent getIntent();
method public long getLastChangedTimestamp();
- method public java.lang.String getPackageName();
+ method public java.lang.String getPackage();
method public int getRank();
- method public java.lang.String getText();
+ method public java.lang.CharSequence getText();
method public int getTextResId();
- method public java.lang.String getTitle();
+ method public java.lang.CharSequence getTitle();
method public int getTitleResId();
method public android.os.UserHandle getUserHandle();
method public boolean hasIconFile();
method public boolean hasIconResource();
method public boolean hasKeyFieldsOnly();
method public boolean hasStringResourcesResolved();
- method public boolean isDisabled();
method public boolean isDynamic();
- method public boolean isFromManifest();
+ method public boolean isEnabled();
+ method public boolean isImmutable();
+ method public boolean isManifestShortcut();
method public boolean isPinned();
method public void writeToParcel(android.os.Parcel, int);
field public static final int CLONE_REMOVE_FOR_CREATOR = 1; // 0x1
@@ -10075,10 +10086,11 @@
field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
field public static final int FLAG_DISABLED = 64; // 0x40
field public static final int FLAG_DYNAMIC = 1; // 0x1
- field public static final int FLAG_FROM_MANIFEST = 32; // 0x20
field public static final int FLAG_HAS_ICON_FILE = 8; // 0x8
field public static final int FLAG_HAS_ICON_RES = 4; // 0x4
+ field public static final int FLAG_IMMUTABLE = 256; // 0x100
field public static final int FLAG_KEY_FIELDS_ONLY = 16; // 0x10
+ field public static final int FLAG_MANIFEST = 32; // 0x20
field public static final int FLAG_PINNED = 2; // 0x2
field public static final int FLAG_STRINGS_RESOLVED = 128; // 0x80
field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
@@ -10087,19 +10099,16 @@
public static class ShortcutInfo.Builder {
ctor public ShortcutInfo.Builder(android.content.Context);
method public android.content.pm.ShortcutInfo build();
- method public android.content.pm.ShortcutInfo.Builder setActivityComponent(android.content.ComponentName);
+ 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 setDisabledMessageResId(int);
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 setRank(int);
method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String);
- method public android.content.pm.ShortcutInfo.Builder setTextResId(int);
method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
- method public android.content.pm.ShortcutInfo.Builder setTitleResId(int);
}
public class ShortcutManager {
@@ -10107,9 +10116,12 @@
method public void disableShortcuts(java.util.List<java.lang.String>);
method public void disableShortcuts(java.util.List<java.lang.String>, int);
method public void disableShortcuts(java.util.List<java.lang.String>, java.lang.String);
+ method public void enableShortcuts(java.util.List<java.lang.String>);
method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts();
- method public int getIconMaxDimensions();
- method public int getMaxDynamicShortcutCount();
+ method public int getIconMaxHeight();
+ method public int getIconMaxWidth();
+ method public java.util.List<android.content.pm.ShortcutInfo> getManifestShortcuts();
+ method public int getMaxShortcutCountForActivity();
method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts();
method public long getRateLimitResetTime();
method public int getRemainingCallCount();
diff --git a/api/system-current.txt b/api/system-current.txt
index 3e8d6fa..505052d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1216,6 +1216,15 @@
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 = 16844078; // 0x101052e
+ field public static final int shortcutDisabledMessage = 16844077; // 0x101052d
+ field public static final int shortcutIcon = 16844074; // 0x101052a
+ field public static final int shortcutId = 16844072; // 0x1010528
+ field public static final int shortcutIntentAction = 16844079; // 0x101052f
+ field public static final int shortcutIntentData = 16844080; // 0x1010530
+ field public static final int shortcutRank = 16844073; // 0x1010529
+ field public static final int shortcutText = 16844076; // 0x101052c
+ field public static final int shortcutTitle = 16844075; // 0x101052b
field public static final int shouldDisableView = 16843246; // 0x10101ee
field public static final int showAsAction = 16843481; // 0x10102d9
field public static final int showDefault = 16843258; // 0x10101fa
@@ -9887,13 +9896,14 @@
public static class LauncherApps.ShortcutQuery {
ctor public LauncherApps.ShortcutQuery();
- method public void setActivity(android.content.ComponentName);
- method public void setChangedSince(long);
- method public void setPackage(java.lang.String);
- method public void setQueryFlags(int);
- method public void setShortcutIds(java.util.List<java.lang.String>);
+ method public android.content.pm.LauncherApps.ShortcutQuery setActivity(android.content.ComponentName);
+ method public android.content.pm.LauncherApps.ShortcutQuery setChangedSince(long);
+ method public android.content.pm.LauncherApps.ShortcutQuery setPackage(java.lang.String);
+ method public android.content.pm.LauncherApps.ShortcutQuery setQueryFlags(int);
+ method public android.content.pm.LauncherApps.ShortcutQuery setShortcutIds(java.util.List<java.lang.String>);
field public static final int FLAG_GET_DYNAMIC = 1; // 0x1
field public static final int FLAG_GET_KEY_FIELDS_ONLY = 4; // 0x4
+ field public static final int FLAG_GET_MANIFEST = 8; // 0x8
field public static final int FLAG_GET_PINNED = 2; // 0x2
}
@@ -10467,29 +10477,30 @@
public final class ShortcutInfo implements android.os.Parcelable {
method public int describeContents();
- method public android.content.ComponentName getActivityComponent();
+ method public android.content.ComponentName getActivity();
method public java.util.Set<java.lang.String> getCategories();
- method public java.lang.String getDisabledMessage();
+ method public java.lang.CharSequence getDisabledMessage();
method public int getDisabledMessageResId();
method public android.os.PersistableBundle getExtras();
method public int getIconResourceId();
method public java.lang.String getId();
method public android.content.Intent getIntent();
method public long getLastChangedTimestamp();
- method public java.lang.String getPackageName();
+ method public java.lang.String getPackage();
method public int getRank();
- method public java.lang.String getText();
+ method public java.lang.CharSequence getText();
method public int getTextResId();
- method public java.lang.String getTitle();
+ method public java.lang.CharSequence getTitle();
method public int getTitleResId();
method public android.os.UserHandle getUserHandle();
method public boolean hasIconFile();
method public boolean hasIconResource();
method public boolean hasKeyFieldsOnly();
method public boolean hasStringResourcesResolved();
- method public boolean isDisabled();
method public boolean isDynamic();
- method public boolean isFromManifest();
+ method public boolean isEnabled();
+ method public boolean isImmutable();
+ method public boolean isManifestShortcut();
method public boolean isPinned();
method public void writeToParcel(android.os.Parcel, int);
field public static final int CLONE_REMOVE_FOR_CREATOR = 1; // 0x1
@@ -10498,10 +10509,11 @@
field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
field public static final int FLAG_DISABLED = 64; // 0x40
field public static final int FLAG_DYNAMIC = 1; // 0x1
- field public static final int FLAG_FROM_MANIFEST = 32; // 0x20
field public static final int FLAG_HAS_ICON_FILE = 8; // 0x8
field public static final int FLAG_HAS_ICON_RES = 4; // 0x4
+ field public static final int FLAG_IMMUTABLE = 256; // 0x100
field public static final int FLAG_KEY_FIELDS_ONLY = 16; // 0x10
+ field public static final int FLAG_MANIFEST = 32; // 0x20
field public static final int FLAG_PINNED = 2; // 0x2
field public static final int FLAG_STRINGS_RESOLVED = 128; // 0x80
field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
@@ -10510,19 +10522,16 @@
public static class ShortcutInfo.Builder {
ctor public ShortcutInfo.Builder(android.content.Context);
method public android.content.pm.ShortcutInfo build();
- method public android.content.pm.ShortcutInfo.Builder setActivityComponent(android.content.ComponentName);
+ 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 setDisabledMessageResId(int);
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 setRank(int);
method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String);
- method public android.content.pm.ShortcutInfo.Builder setTextResId(int);
method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
- method public android.content.pm.ShortcutInfo.Builder setTitleResId(int);
}
public class ShortcutManager {
@@ -10530,9 +10539,12 @@
method public void disableShortcuts(java.util.List<java.lang.String>);
method public void disableShortcuts(java.util.List<java.lang.String>, int);
method public void disableShortcuts(java.util.List<java.lang.String>, java.lang.String);
+ method public void enableShortcuts(java.util.List<java.lang.String>);
method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts();
- method public int getIconMaxDimensions();
- method public int getMaxDynamicShortcutCount();
+ method public int getIconMaxHeight();
+ method public int getIconMaxWidth();
+ method public java.util.List<android.content.pm.ShortcutInfo> getManifestShortcuts();
+ method public int getMaxShortcutCountForActivity();
method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts();
method public long getRateLimitResetTime();
method public int getRemainingCallCount();
diff --git a/api/test-current.txt b/api/test-current.txt
index 1448c6c..a7d4f9a 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1106,6 +1106,15 @@
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 = 16844078; // 0x101052e
+ field public static final int shortcutDisabledMessage = 16844077; // 0x101052d
+ field public static final int shortcutIcon = 16844074; // 0x101052a
+ field public static final int shortcutId = 16844072; // 0x1010528
+ field public static final int shortcutIntentAction = 16844079; // 0x101052f
+ field public static final int shortcutIntentData = 16844080; // 0x1010530
+ field public static final int shortcutRank = 16844073; // 0x1010529
+ field public static final int shortcutText = 16844076; // 0x101052c
+ field public static final int shortcutTitle = 16844075; // 0x101052b
field public static final int shouldDisableView = 16843246; // 0x10101ee
field public static final int showAsAction = 16843481; // 0x10102d9
field public static final int showDefault = 16843258; // 0x10101fa
@@ -9546,13 +9555,14 @@
public static class LauncherApps.ShortcutQuery {
ctor public LauncherApps.ShortcutQuery();
- method public void setActivity(android.content.ComponentName);
- method public void setChangedSince(long);
- method public void setPackage(java.lang.String);
- method public void setQueryFlags(int);
- method public void setShortcutIds(java.util.List<java.lang.String>);
+ method public android.content.pm.LauncherApps.ShortcutQuery setActivity(android.content.ComponentName);
+ method public android.content.pm.LauncherApps.ShortcutQuery setChangedSince(long);
+ method public android.content.pm.LauncherApps.ShortcutQuery setPackage(java.lang.String);
+ method public android.content.pm.LauncherApps.ShortcutQuery setQueryFlags(int);
+ method public android.content.pm.LauncherApps.ShortcutQuery setShortcutIds(java.util.List<java.lang.String>);
field public static final int FLAG_GET_DYNAMIC = 1; // 0x1
field public static final int FLAG_GET_KEY_FIELDS_ONLY = 4; // 0x4
+ field public static final int FLAG_GET_MANIFEST = 8; // 0x8
field public static final int FLAG_GET_PINNED = 2; // 0x2
}
@@ -10057,29 +10067,30 @@
public final class ShortcutInfo implements android.os.Parcelable {
method public int describeContents();
- method public android.content.ComponentName getActivityComponent();
+ method public android.content.ComponentName getActivity();
method public java.util.Set<java.lang.String> getCategories();
- method public java.lang.String getDisabledMessage();
+ method public java.lang.CharSequence getDisabledMessage();
method public int getDisabledMessageResId();
method public android.os.PersistableBundle getExtras();
method public int getIconResourceId();
method public java.lang.String getId();
method public android.content.Intent getIntent();
method public long getLastChangedTimestamp();
- method public java.lang.String getPackageName();
+ method public java.lang.String getPackage();
method public int getRank();
- method public java.lang.String getText();
+ method public java.lang.CharSequence getText();
method public int getTextResId();
- method public java.lang.String getTitle();
+ method public java.lang.CharSequence getTitle();
method public int getTitleResId();
method public android.os.UserHandle getUserHandle();
method public boolean hasIconFile();
method public boolean hasIconResource();
method public boolean hasKeyFieldsOnly();
method public boolean hasStringResourcesResolved();
- method public boolean isDisabled();
method public boolean isDynamic();
- method public boolean isFromManifest();
+ method public boolean isEnabled();
+ method public boolean isImmutable();
+ method public boolean isManifestShortcut();
method public boolean isPinned();
method public void writeToParcel(android.os.Parcel, int);
field public static final int CLONE_REMOVE_FOR_CREATOR = 1; // 0x1
@@ -10088,10 +10099,11 @@
field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
field public static final int FLAG_DISABLED = 64; // 0x40
field public static final int FLAG_DYNAMIC = 1; // 0x1
- field public static final int FLAG_FROM_MANIFEST = 32; // 0x20
field public static final int FLAG_HAS_ICON_FILE = 8; // 0x8
field public static final int FLAG_HAS_ICON_RES = 4; // 0x4
+ field public static final int FLAG_IMMUTABLE = 256; // 0x100
field public static final int FLAG_KEY_FIELDS_ONLY = 16; // 0x10
+ field public static final int FLAG_MANIFEST = 32; // 0x20
field public static final int FLAG_PINNED = 2; // 0x2
field public static final int FLAG_STRINGS_RESOLVED = 128; // 0x80
field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
@@ -10100,19 +10112,16 @@
public static class ShortcutInfo.Builder {
ctor public ShortcutInfo.Builder(android.content.Context);
method public android.content.pm.ShortcutInfo build();
- method public android.content.pm.ShortcutInfo.Builder setActivityComponent(android.content.ComponentName);
+ 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 setDisabledMessageResId(int);
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 setRank(int);
method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String);
- method public android.content.pm.ShortcutInfo.Builder setTextResId(int);
method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
- method public android.content.pm.ShortcutInfo.Builder setTitleResId(int);
}
public class ShortcutManager {
@@ -10121,9 +10130,12 @@
method public void disableShortcuts(java.util.List<java.lang.String>);
method public void disableShortcuts(java.util.List<java.lang.String>, int);
method public void disableShortcuts(java.util.List<java.lang.String>, java.lang.String);
+ method public void enableShortcuts(java.util.List<java.lang.String>);
method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts();
- method public int getIconMaxDimensions();
- method public int getMaxDynamicShortcutCount();
+ method public int getIconMaxHeight();
+ method public int getIconMaxWidth();
+ method public java.util.List<android.content.pm.ShortcutInfo> getManifestShortcuts();
+ method public int getMaxShortcutCountForActivity();
method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts();
method public long getRateLimitResetTime();
method public int getRemainingCallCount();
diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl
index 06200bf..1c373f9 100644
--- a/core/java/android/content/pm/IShortcutService.aidl
+++ b/core/java/android/content/pm/IShortcutService.aidl
@@ -28,6 +28,8 @@
ParceledListSlice getDynamicShortcuts(String packageName, int userId);
+ ParceledListSlice getManifestShortcuts(String packageName, int userId);
+
boolean addDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList,
int userId);
@@ -42,6 +44,8 @@
void disableShortcuts(String packageName, in List shortcutIds, String disabledMessage,
int disabledMessageResId, int userId);
+ void enableShortcuts(String packageName, in List shortcutIds, int userId);
+
int getMaxDynamicShortcutCount(String packageName, int userId);
int getRemainingCallCount(String packageName, int userId);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index acd85cb..7ee7048 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -188,6 +188,11 @@
public static final int FLAG_GET_PINNED = 1 << 1;
/**
+ * Include manifest shortcuts in the result.
+ */
+ public static final int FLAG_GET_MANIFEST = 1 << 3;
+
+ /**
* Requests "key" fields only. See {@link ShortcutInfo#hasKeyFieldsOnly()} for which
* fields are available.
*/
@@ -198,6 +203,7 @@
value = {
FLAG_GET_DYNAMIC,
FLAG_GET_PINNED,
+ FLAG_GET_MANIFEST,
FLAG_GET_KEY_FIELDS_ONLY,
})
@Retention(RetentionPolicy.SOURCE)
@@ -224,39 +230,44 @@
* If non-zero, returns only shortcuts that have been added or updated since the timestamp,
* which is a milliseconds since the Epoch.
*/
- public void setChangedSince(long changedSince) {
+ public ShortcutQuery setChangedSince(long changedSince) {
mChangedSince = changedSince;
+ return this;
}
/**
* If non-null, returns only shortcuts from the package.
*/
- public void setPackage(@Nullable String packageName) {
+ public ShortcutQuery setPackage(@Nullable String packageName) {
mPackage = packageName;
+ return this;
}
/**
* If non-null, return only the specified shortcuts by ID. When setting this field,
* a packange name must also be set with {@link #setPackage}.
*/
- public void setShortcutIds(@Nullable List<String> shortcutIds) {
+ public ShortcutQuery setShortcutIds(@Nullable List<String> shortcutIds) {
mShortcutIds = shortcutIds;
+ return this;
}
/**
- * If non-null, returns only shortcuts associated with the activity, which are
- * {@link ShortcutInfo}s that have null {@link ShortcutInfo#getActivityComponent()}, or
- * {@link ShortcutInfo#getActivityComponent()} equals to {@code activity}.
+ * If non-null, returns only shortcuts associated with the activity; i.e.
+ * {@link ShortcutInfo}s whose {@link ShortcutInfo#getActivity()} are equal
+ * to {@code activity}.
*/
- public void setActivity(@Nullable ComponentName activity) {
+ public ShortcutQuery setActivity(@Nullable ComponentName activity) {
mActivity = activity;
+ return this;
}
/**
* Set query options.
*/
- public void setQueryFlags(@QueryFlags int queryFlags) {
+ public ShortcutQuery setQueryFlags(@QueryFlags int queryFlags) {
mQueryFlags = queryFlags;
+ return this;
}
}
@@ -525,7 +536,7 @@
*/
public ParcelFileDescriptor getShortcutIconFd(
@NonNull ShortcutInfo shortcut) {
- return getShortcutIconFd(shortcut.getPackageName(), shortcut.getId(),
+ return getShortcutIconFd(shortcut.getPackage(), shortcut.getId(),
shortcut.getUserId());
}
@@ -555,14 +566,14 @@
}
}
- /** TODO Javadoc */
+ /** TODO Javadoc (not implemented yet) */
public Drawable getShortcutIconDrawable(int density) {
- throw new RuntimeException("TODO implement it");
+ throw new RuntimeException("TODO not implemented yet");
}
- /** TODO Javadoc */
+ /** TODO Javadoc (not implemented yet) */
public Drawable getShortcutBadgedIconDrawable(int density) {
- throw new RuntimeException("TODO implement it");
+ throw new RuntimeException("TODO not implemented yet");
}
/**
@@ -600,7 +611,7 @@
*/
public boolean startShortcut(@NonNull ShortcutInfo shortcut,
@Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions) {
- return startShortcut(shortcut.getPackageName(), shortcut.getId(),
+ return startShortcut(shortcut.getPackage(), shortcut.getId(),
sourceBounds, startActivityOptions,
shortcut.getUserId());
}
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 3648d9e..c83aa22 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -70,7 +70,7 @@
public static final int FLAG_KEY_FIELDS_ONLY = 1 << 4;
/* @hide */
- public static final int FLAG_FROM_MANIFEST = 1 << 5;
+ public static final int FLAG_MANIFEST = 1 << 5;
/* @hide */
public static final int FLAG_DISABLED = 1 << 6;
@@ -78,6 +78,9 @@
/* @hide */
public static final int FLAG_STRINGS_RESOLVED = 1 << 7;
+ /* @hide */
+ public static final int FLAG_IMMUTABLE = 1 << 8;
+
/** @hide */
@IntDef(flag = true,
value = {
@@ -86,9 +89,10 @@
FLAG_HAS_ICON_RES,
FLAG_HAS_ICON_FILE,
FLAG_KEY_FIELDS_ONLY,
- FLAG_FROM_MANIFEST,
+ FLAG_MANIFEST,
FLAG_DISABLED,
FLAG_STRINGS_RESOLVED,
+ FLAG_IMMUTABLE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ShortcutFlags {}
@@ -133,7 +137,7 @@
private final String mPackageName;
@Nullable
- private ComponentName mActivityComponent;
+ private ComponentName mActivity;
@Nullable
private Icon mIcon;
@@ -141,17 +145,17 @@
private int mTitleResId;
@Nullable
- private String mTitle;
+ private CharSequence mTitle;
private int mTextResId;
@Nullable
- private String mText;
+ private CharSequence mText;
private int mDisabledMessageResId;
@Nullable
- private String mDisabledMessage;
+ private CharSequence mDisabledMessage;
@Nullable
private ArraySet<String> mCategories;
@@ -196,7 +200,7 @@
// Note we can't do other null checks here because SM.updateShortcuts() takes partial
// information.
mPackageName = b.mContext.getPackageName();
- mActivityComponent = b.mActivityComponent;
+ mActivity = b.mActivity;
mIcon = b.mIcon;
mTitle = b.mTitle;
mTitleResId = b.mTitleResId;
@@ -229,7 +233,7 @@
*/
public void enforceMandatoryFields() {
Preconditions.checkStringNotEmpty(mId, "Shortcut ID must be provided");
- Preconditions.checkNotNull(mActivityComponent, "activityComponent must be provided");
+ Preconditions.checkNotNull(mActivity, "activity must be provided");
if (mTitle == null && mTitleResId == 0) {
throw new IllegalArgumentException("Shortcut title must be provided");
}
@@ -250,7 +254,7 @@
mIconResourceId = source.mIconResourceId;
if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) {
- mActivityComponent = source.mActivityComponent;
+ mActivity = source.mActivity;
if ((cloneFlags & CLONE_REMOVE_ICON) == 0) {
mIcon = source.mIcon;
@@ -309,6 +313,17 @@
}
/**
+ * @hide
+ */
+ public void ensureUpdatableWith(ShortcutInfo source) {
+ Preconditions.checkState(mUserId == source.mUserId, "Owner User ID must match");
+ Preconditions.checkState(mId.equals(source.mId), "ID must match");
+ Preconditions.checkState(mPackageName.equals(source.mPackageName),
+ "Package name must match");
+ Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable");
+ }
+
+ /**
* Copy non-null/zero fields from another {@link ShortcutInfo}. Only "public" information
* will be overwritten. The timestamp will be updated.
*
@@ -316,16 +331,15 @@
* - mBitmapPath will not change
* - Current time will be set to timestamp
*
+ * @throws IllegalStateException if source is not compatible.
+ *
* @hide
*/
public void copyNonNullFieldsFrom(ShortcutInfo source) {
- Preconditions.checkState(mUserId == source.mUserId, "Owner User ID must match");
- Preconditions.checkState(mId.equals(source.mId), "ID must match");
- Preconditions.checkState(mPackageName.equals(source.mPackageName),
- "Package name must match");
+ ensureUpdatableWith(source);
- if (source.mActivityComponent != null) {
- mActivityComponent = source.mActivityComponent;
+ if (source.mActivity != null) {
+ mActivity = source.mActivity;
}
if (source.mIcon != null) {
@@ -401,21 +415,21 @@
private String mId;
- private ComponentName mActivityComponent;
+ private ComponentName mActivity;
private Icon mIcon;
private int mTitleResId;
- private String mTitle;
+ private CharSequence mTitle;
private int mTextResId;
- private String mText;
+ private CharSequence mText;
private int mDisabledMessageResId;
- private String mDisabledMessage;
+ private CharSequence mDisabledMessage;
private Set<String> mCategories;
@@ -451,8 +465,8 @@
* a hint to the launcher app about which launcher icon to associate this shortcut with.
*/
@NonNull
- public Builder setActivityComponent(@NonNull ComponentName activityComponent) {
- mActivityComponent = Preconditions.checkNotNull(activityComponent, "activityComponent");
+ public Builder setActivity(@NonNull ComponentName activity) {
+ mActivity = Preconditions.checkNotNull(activity, "activity");
return this;
}
@@ -476,7 +490,7 @@
return this;
}
- /** TODO Javadoc */
+ /** @hide */
public Builder setTitleResId(int titleResId) {
Preconditions.checkState(mTitle == null, "title already set");
mTitleResId = titleResId;
@@ -496,7 +510,7 @@
return this;
}
- /** TODO Javadoc */
+ /** @hide */
public Builder setTextResId(int textResId) {
Preconditions.checkState(mText == null, "text already set");
mTextResId = textResId;
@@ -516,7 +530,7 @@
return this;
}
- /** TODO Javadoc */
+ /** @hide */
public Builder setDisabledMessageResId(int disabledMessageResId) {
Preconditions.checkState(mDisabledMessage == null, "disabledMessage already set");
mDisabledMessageResId = disabledMessageResId;
@@ -595,7 +609,7 @@
* Return the package name of the creator application.
*/
@NonNull
- public String getPackageName() {
+ public String getPackage() {
return mPackageName;
}
@@ -606,11 +620,11 @@
* <p>This has nothing to do with the activity that this shortcut will launch. This is
* a hint to the launcher app that on which launcher icon this shortcut should be shown.
*
- * @see Builder#setActivityComponent
+ * @see Builder#setActivity
*/
@Nullable
- public ComponentName getActivityComponent() {
- return mActivityComponent;
+ public ComponentName getActivity() {
+ return mActivity;
}
/**
@@ -634,7 +648,7 @@
* {@link #hasKeyFieldsOnly()} is true.
*/
@Nullable
- public String getTitle() {
+ public CharSequence getTitle() {
return mTitle;
}
@@ -647,7 +661,7 @@
* Return the shortcut text.
*/
@Nullable
- public String getText() {
+ public CharSequence getText() {
return mText;
}
@@ -660,7 +674,7 @@
* Return the message that should be shown when a shortcut in disabled state is launched.
*/
@Nullable
- public String getDisabledMessage() {
+ public CharSequence getDisabledMessage() {
return mDisabledMessage;
}
@@ -788,18 +802,67 @@
}
/**
- * Return whether a shortcut is published via manifest or not. If true, the shortcut is
- * immutable.
+ * Return whether a shortcut is published via AndroidManifest.xml or not. If {@code true},
+ * it's also {@link #isImmutable()}.
+ *
+ * <p>When an app is upgraded and a shortcut is no longer published from AndroidManifest.xml,
+ * this will be set to {@code false}. If the shortcut is not pinned, then it'll just disappear.
+ * However, if it's pinned, it will still be alive, and {@link #isEnabled()} will be
+ * {@code false} and {@link #isImmutable()} will be {@code true}.
+ *
+ * <p>NOTE this is whether a shortcut is published from the <b>current version's</b>
+ * AndroidManifest.xml.
*/
- public boolean isFromManifest() {
- return hasFlags(FLAG_FROM_MANIFEST);
+ public boolean isManifestShortcut() {
+ return hasFlags(FLAG_MANIFEST);
}
- /** Return whether a shortcut is disabled by publisher or not. */
- public boolean isDisabled() {
+ /**
+ * @return true if pinned but neither dynamic nor manifest.
+ * @hide
+ */
+ public boolean isFloating() {
+ return isPinned() && !(isDynamic() || isManifestShortcut());
+ }
+
+ /** @hide */
+ public boolean isOriginallyFromManifest() {
+ return hasFlags(FLAG_IMMUTABLE);
+ }
+
+ /**
+ * Return if a shortcut is immutable, in which case it cannot be modified with any of
+ * {@link ShortcutManager} APIs.
+ *
+ * <p>All manifest shortcuts are immutable. When a manifest shortcut is pinned and then
+ * disabled because the app is upgraded and its AndroidManifest.xml no longer publishes it,
+ * {@link #isManifestShortcut} returns {@code false}, but it is still immutable.
+ *
+ * <p>All shortcuts originally published via the {@link ShortcutManager} APIs
+ * are all mutable.
+ */
+ public boolean isImmutable() {
+ return hasFlags(FLAG_IMMUTABLE);
+ }
+
+ /**
+ * Returns {@code false} if a shortcut is disabled with
+ * {@link ShortcutManager#disableShortcuts}.
+ */
+ public boolean isEnabled() {
return !hasFlags(FLAG_DISABLED);
}
+ /** @hide */
+ public boolean isAlive() {
+ return hasFlags(FLAG_PINNED) || hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_MANIFEST);
+ }
+
+ /** @hide */
+ public boolean usesQuota() {
+ return hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_MANIFEST);
+ }
+
/**
* Return whether a shortcut's icon is a resource in the owning package.
*
@@ -833,7 +896,7 @@
* following fields are available.
* <ul>
* <li>{@link #getId()}
- * <li>{@link #getPackageName()}
+ * <li>{@link #getPackage()}
* <li>{@link #getLastChangedTimestamp()}
* <li>{@link #isDynamic()}
* <li>{@link #isPinned()}
@@ -888,19 +951,31 @@
mBitmapPath = bitmapPath;
}
+ /** @hide */
+ public void setDisabledMessageResId(int disabledMessageResId) {
+ mDisabledMessageResId = disabledMessageResId;
+ mDisabledMessage = null;
+ }
+
+ /** @hide */
+ public void setDisabledMessage(String disabledMessage) {
+ mDisabledMessage = disabledMessage;
+ mDisabledMessageResId = 0;
+ }
+
private ShortcutInfo(Parcel source) {
final ClassLoader cl = getClass().getClassLoader();
mUserId = source.readInt();
mId = source.readString();
mPackageName = source.readString();
- mActivityComponent = source.readParcelable(cl);
+ mActivity = source.readParcelable(cl);
mIcon = source.readParcelable(cl);
- mTitle = source.readString();
+ mTitle = source.readCharSequence();
mTitleResId = source.readInt();
- mText = source.readString();
+ mText = source.readCharSequence();
mTextResId = source.readInt();
- mDisabledMessage = source.readString();
+ mDisabledMessage = source.readCharSequence();
mDisabledMessageResId = source.readInt();
mIntent = source.readParcelable(cl);
mIntentPersistableExtras = source.readParcelable(cl);
@@ -927,13 +1002,13 @@
dest.writeInt(mUserId);
dest.writeString(mId);
dest.writeString(mPackageName);
- dest.writeParcelable(mActivityComponent, flags);
+ dest.writeParcelable(mActivity, flags);
dest.writeParcelable(mIcon, flags);
- dest.writeString(mTitle);
+ dest.writeCharSequence(mTitle);
dest.writeInt(mTitleResId);
- dest.writeString(mText);
+ dest.writeCharSequence(mText);
dest.writeInt(mTextResId);
- dest.writeString(mDisabledMessage);
+ dest.writeCharSequence(mDisabledMessage);
dest.writeInt(mDisabledMessageResId);
dest.writeParcelable(mIntent, flags);
@@ -991,18 +1066,43 @@
sb.append("id=");
sb.append(secure ? "***" : mId);
+ sb.append(", flags=0x");
+ sb.append(Integer.toHexString(mFlags));
+ sb.append(" [");
+ if (!isEnabled()) {
+ sb.append("X");
+ }
+ if (isImmutable()) {
+ sb.append("Im");
+ }
+ if (isManifestShortcut()) {
+ sb.append("M");
+ }
+ if (isDynamic()) {
+ sb.append("D");
+ }
+ if (isPinned()) {
+ sb.append("P");
+ }
+ if (hasIconFile()) {
+ sb.append("If");
+ }
+ if (hasIconResource()) {
+ sb.append("Ir");
+ }
+ if (hasKeyFieldsOnly()) {
+ sb.append("K");
+ }
+ if (hasStringResourcesResolved()) {
+ sb.append("Sr");
+ }
+ sb.append("]");
+
sb.append(", packageName=");
sb.append(mPackageName);
- if (isDynamic()) {
- sb.append(", dynamic");
- }
- if (isPinned()) {
- sb.append(", pinned");
- }
-
sb.append(", activity=");
- sb.append(mActivityComponent);
+ sb.append(mActivity);
sb.append(", title=");
sb.append(secure ? "***" : mTitle);
@@ -1040,35 +1140,6 @@
sb.append(", extras=");
sb.append(mExtras);
- sb.append(", flags=");
- sb.append(mFlags);
- sb.append(" [");
- if (hasFlags(FLAG_DISABLED)) {
- sb.append("X");
- }
- if (hasFlags(FLAG_FROM_MANIFEST)) {
- sb.append("M");
- }
- if (hasFlags(FLAG_DYNAMIC)) {
- sb.append("D");
- }
- if (hasFlags(FLAG_PINNED)) {
- sb.append("P");
- }
- if (hasFlags(FLAG_HAS_ICON_FILE)) {
- sb.append("If");
- }
- if (hasFlags(FLAG_HAS_ICON_RES)) {
- sb.append("Ir");
- }
- if (hasFlags(FLAG_KEY_FIELDS_ONLY)) {
- sb.append("K");
- }
- if (hasFlags(FLAG_STRINGS_RESOLVED)) {
- sb.append("S");
- }
- sb.append("]");
-
if (includeInternalData) {
sb.append(", iconRes=");
@@ -1084,16 +1155,16 @@
/** @hide */
public ShortcutInfo(
- @UserIdInt int userId, String id, String packageName, ComponentName activityComponent,
- Icon icon, String title, int titleResId, String text, int textResId,
- String disabledMessage, int disabledMessageResId, Set<String> categories, Intent intent,
- PersistableBundle intentPersistableExtras,
+ @UserIdInt int userId, String id, String packageName, ComponentName activity,
+ Icon icon, CharSequence title, int titleResId, CharSequence text, int textResId,
+ CharSequence disabledMessage, int disabledMessageResId, Set<String> categories,
+ Intent intent, PersistableBundle intentPersistableExtras,
int rank, PersistableBundle extras, long lastChangedTimestamp,
int flags, int iconResId, String bitmapPath) {
mUserId = userId;
mId = id;
mPackageName = packageName;
- mActivityComponent = activityComponent;
+ mActivity = activity;
mIcon = icon;
mTitle = title;
mTitleResId = titleResId;
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 44fa98d..3d214a84 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -28,7 +28,7 @@
// TODO Enhance javadoc
/**
- * <b>TODO: Update to reflect DR changes.</b><br>
+ * <b>TODO: Update to reflect DR changes, such as manifest shortcuts.</b><br>
*
* {@link ShortcutManager} manages shortcuts created by applications.
*
@@ -36,8 +36,8 @@
*
* An application can publish shortcuts with {@link #setDynamicShortcuts(List)} and
* {@link #addDynamicShortcuts(List)}. There can be at most
- * {@link #getMaxDynamicShortcutCount()} number of dynamic shortcuts at a time from the same
- * application.
+ * {@link #getMaxShortcutCountForActivity()} number of dynamic shortcuts at a time from the
+ * same application.
* A dynamic shortcut can be deleted with {@link #removeDynamicShortcuts(List)}, and apps
* can also use {@link #removeAllDynamicShortcuts()} to delete all dynamic shortcuts.
*
@@ -51,7 +51,8 @@
* <p>The number of pinned shortcuts does not affect the number of dynamic shortcuts that can be
* published by an application at a time.
* No matter how many pinned shortcuts that Launcher has for an application, the
- * application can still always publish {@link #getMaxDynamicShortcutCount()} number of dynamic
+ * application can still always publish {@link #getMaxShortcutCountForActivity()} number of
+ * dynamic
* shortcuts.
*
* <h3>Shortcut IDs</h3>
@@ -132,7 +133,7 @@
* @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited.
*
* @throws IllegalArgumentException if {@code shortcutInfoList} contains more than
- * {@link #getMaxDynamicShortcutCount()} shortcuts.
+ * {@link #getMaxShortcutCountForActivity()} shortcuts.
*/
public boolean setDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
try {
@@ -145,7 +146,7 @@
/**
* Return all dynamic shortcuts from the caller application. The number of result items
- * will not exceed the value returned by {@link #getMaxDynamicShortcutCount()}.
+ * will not exceed the value returned by {@link #getMaxShortcutCountForActivity()}.
*/
@NonNull
public List<ShortcutInfo> getDynamicShortcuts() {
@@ -158,6 +159,19 @@
}
/**
+ * TODO Javadoc
+ */
+ @NonNull
+ public List<ShortcutInfo> getManifestShortcuts() {
+ try {
+ return mService.getManifestShortcuts(mContext.getPackageName(), injectMyUserId())
+ .getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Publish list of dynamic shortcuts. If there's already dynamic or pinned shortcuts with
* the same IDs, they will all be updated.
*
@@ -269,9 +283,21 @@
}
/**
- * Return the max number of dynamic shortcuts that each application can have at a time.
+ * TODO Javadoc
*/
- public int getMaxDynamicShortcutCount() {
+ public void enableShortcuts(@NonNull List<String> shortcutIds) {
+ try {
+ mService.enableShortcuts(mContext.getPackageName(), shortcutIds, injectMyUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the max number of dynamic shortcuts + manifest shortcuts that each launcehr icon
+ * can have at a time.
+ */
+ public int getMaxShortcutCountForActivity() {
try {
return mService.getMaxDynamicShortcutCount(mContext.getPackageName(), injectMyUserId());
} catch (RemoteException e) {
@@ -308,10 +334,23 @@
}
/**
- * Return the max width and height for icons, in pixels.
+ * Return the max width for icons, in pixels.
*/
- public int getIconMaxDimensions() {
+ public int getIconMaxWidth() {
try {
+ // TODO Implement it properly using xdpi.
+ return mService.getIconMaxDimensions(mContext.getPackageName(), injectMyUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the max height for icons, in pixels.
+ */
+ public int getIconMaxHeight() {
+ try {
+ // TODO Implement it properly using ydpi.
return mService.getIconMaxDimensions(mContext.getPackageName(), injectMyUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index e0fbca1..3259907 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8237,4 +8237,17 @@
color. -->
<attr name="colorBackground" />
</declare-styleable>
+
+ <declare-styleable name="Shortcut">
+ <attr name="shortcutId" format="string" />
+ <attr name="enabled" format="boolean" />
+ <attr name="shortcutRank" format="integer" />
+ <attr name="shortcutIcon" format="reference" />
+ <attr name="shortcutTitle" format="reference" />
+ <attr name="shortcutText" format="reference" />
+ <attr name="shortcutDisabledMessage" format="reference" />
+ <attr name="shortcutCategories" format="string" />
+ <attr name="shortcutIntentAction" format="string" />
+ <attr name="shortcutIntentData" format="string" />
+ </declare-styleable>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 6797799..2738dc3 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2728,4 +2728,18 @@
<public type="id" name="icon_frame" id="0x0102003e" />
<public type="id" name="list_container" id="0x0102003f" />
<public type="id" name="switch_widget" id="0x01020040" />
+
+ <!-- ===============================================================
+ Resources added in version N MR1 of the platform
+ =============================================================== -->
+ <eat-comment />
+ <public type="attr" name="shortcutId" />
+ <public type="attr" name="shortcutRank" />
+ <public type="attr" name="shortcutIcon" />
+ <public type="attr" name="shortcutTitle" />
+ <public type="attr" name="shortcutText" />
+ <public type="attr" name="shortcutDisabledMessage" />
+ <public type="attr" name="shortcutCategories" />
+ <public type="attr" name="shortcutIntentAction" />
+ <public type="attr" name="shortcutIntentData" />
</resources>
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index db2b9f4..e667838 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -36,6 +36,8 @@
/**
* Launcher information used by {@link ShortcutService}.
+ *
+ * All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
*/
class ShortcutLauncher extends ShortcutPackageItem {
private static final String TAG = ShortcutService.TAG;
@@ -125,7 +127,8 @@
if (si == null) {
continue;
}
- if (si.isDynamic() || (prevSet != null && prevSet.contains(id))) {
+ if (si.isDynamic() || si.isManifestShortcut()
+ || (prevSet != null && prevSet.contains(id))) {
newSet.add(id);
}
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index c298683..bca3777 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -20,6 +20,7 @@
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.PackageInfo;
import android.content.pm.ShortcutInfo;
import android.os.PersistableBundle;
import android.text.format.Formatter;
@@ -28,6 +29,7 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -44,6 +46,11 @@
/**
* Package information used by {@link ShortcutService}.
+ * User information used by {@link ShortcutService}.
+ *
+ * All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
+ *
+ * TODO Max dynamic shortcuts cap should be per activity.
*/
class ShortcutPackage extends ShortcutPackageItem {
private static final String TAG = ShortcutService.TAG;
@@ -55,7 +62,6 @@
private static final String TAG_CATEGORIES = "categories";
private static final String ATTR_NAME = "name";
- private static final String ATTR_DYNAMIC_COUNT = "dynamic-count";
private static final String ATTR_CALL_COUNT = "call-count";
private static final String ATTR_LAST_RESET = "last-reset";
private static final String ATTR_ID = "id";
@@ -84,11 +90,6 @@
final private ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
/**
- * # of dynamic shortcuts.
- */
- private int mDynamicShortcutCount = 0;
-
- /**
* # of times the package has called rate-limited APIs.
*/
private int mApiCallCount;
@@ -129,19 +130,19 @@
* exists (as opposed to Launcher trying to fetch shortcuts from a non-existent package), so
* we do some initialization for the package.
*/
- private void onShortcutPublish() {
+ private void ensurePackageVersionInfo() {
// Make sure we have the version code for the app. We need the version code in
// handlePackageUpdated().
if (getPackageInfo().getVersionCode() < 0) {
final ShortcutService s = mShortcutUser.mService;
- final int versionCode = s.getApplicationVersionCode(getPackageName(), getOwnerUserId());
- if (ShortcutService.DEBUG) {
- Slog.d(TAG, String.format("Package %s version = %d", getPackageName(),
- versionCode));
- }
- if (versionCode >= 0) {
- getPackageInfo().setVersionCode(versionCode);
+ final PackageInfo pi = s.getPackageInfo(getPackageName(), getOwnerUserId());
+ if (pi != null) {
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, String.format("Package %s version = %d", getPackageName(),
+ pi.versionCode));
+ }
+ getPackageInfo().updateVersionInfo(pi);
s.scheduleSaveUser(getOwnerUserId());
}
}
@@ -168,17 +169,42 @@
return mShortcuts.get(id);
}
- private ShortcutInfo deleteShortcut(@NonNull String id) {
+ private void ensureNotImmutable(@Nullable ShortcutInfo shortcut) {
+ if (shortcut != null && shortcut.isImmutable()) {
+ throw new IllegalArgumentException(
+ "Manifest shortcut ID=" + shortcut.getId()
+ + " may not be manipulated via APIs");
+ }
+ }
+
+ private void ensureNotImmutable(@NonNull String id) {
+ ensureNotImmutable(mShortcuts.get(id));
+ }
+
+ public void ensureImmutableShortcutsNotIncludedWithIds(@NonNull List<String> shortcutIds) {
+ for (int i = shortcutIds.size() - 1; i >= 0; i--) {
+ ensureNotImmutable(shortcutIds.get(i));
+ }
+ }
+
+ public void ensureImmutableShortcutsNotIncluded(@NonNull List<ShortcutInfo> shortcuts) {
+ for (int i = shortcuts.size() - 1; i >= 0; i--) {
+ ensureNotImmutable(shortcuts.get(i).getId());
+ }
+ }
+
+ private ShortcutInfo deleteShortcutInner(@NonNull String id) {
final ShortcutInfo shortcut = mShortcuts.remove(id);
if (shortcut != null) {
mShortcutUser.mService.removeIcon(getPackageUserId(), shortcut);
- shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED);
+ shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED
+ | ShortcutInfo.FLAG_MANIFEST);
}
return shortcut;
}
- void addShortcut(@NonNull ShortcutInfo newShortcut) {
- deleteShortcut(newShortcut.getId());
+ private void addShortcutInner(@NonNull ShortcutInfo newShortcut) {
+ deleteShortcutInner(newShortcut.getId());
mShortcutUser.mService.saveIconAndFixUpShortcut(getPackageUserId(), newShortcut);
mShortcuts.put(newShortcut.getId(), newShortcut);
}
@@ -188,39 +214,41 @@
*
* It checks the max number of dynamic shortcuts.
*/
- public void addDynamicShortcut(@NonNull ShortcutInfo newShortcut) {
+ public void addOrUpdateDynamicShortcut(@NonNull ShortcutInfo newShortcut) {
- onShortcutPublish();
+ Preconditions.checkArgument(newShortcut.isEnabled(),
+ "add/setDynamicShortcuts() cannot publish disabled shortcuts");
+
+ ensurePackageVersionInfo();
newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
final boolean wasPinned;
- final int newDynamicCount;
if (oldShortcut == null) {
wasPinned = false;
- newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut.
} else {
+ // It's an update case.
+ // Make sure the target is updatable. (i.e. should be mutable.)
+ oldShortcut.ensureUpdatableWith(newShortcut);
+
wasPinned = oldShortcut.isPinned();
- if (oldShortcut.isDynamic()) {
- newDynamicCount = mDynamicShortcutCount; // not adding a dynamic shortcut.
- } else {
- newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut.
+ if (!oldShortcut.isEnabled()) {
+ newShortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
}
}
- // Make sure there's still room.
- mShortcutUser.mService.enforceMaxDynamicShortcuts(newDynamicCount);
+ // TODO Check max dynamic count.
+ // mShortcutUser.mService.enforceMaxDynamicShortcuts(newDynamicCount);
// Okay, make it dynamic and add.
if (wasPinned) {
newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
}
- addShortcut(newShortcut);
- mDynamicShortcutCount = newDynamicCount;
+ addShortcutInner(newShortcut);
}
/**
@@ -232,7 +260,7 @@
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
- if (si.isPinned() || si.isDynamic()) continue;
+ if (si.isAlive()) continue;
if (removeList == null) {
removeList = new ArrayList<>();
@@ -241,7 +269,7 @@
}
if (removeList != null) {
for (int i = removeList.size() - 1; i >= 0; i--) {
- deleteShortcut(removeList.get(i));
+ deleteShortcutInner(removeList.get(i));
}
}
}
@@ -250,29 +278,68 @@
* Remove all dynamic shortcuts.
*/
public void deleteAllDynamicShortcuts() {
+ boolean changed = false;
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+ final ShortcutInfo si = mShortcuts.valueAt(i);
+ if (si.isDynamic()) {
+ changed = true;
+ si.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+ }
}
- removeOrphans();
- mDynamicShortcutCount = 0;
+ if (changed) {
+ removeOrphans();
+ }
}
/**
* Remove a dynamic shortcut by ID.
*/
public void deleteDynamicWithId(@NonNull String shortcutId) {
+ deleteOrDisableWithId(shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false);
+ }
+
+ public void disableWithId(@NonNull String shortcutId, String disabledMessage,
+ int disabledMessageResId, boolean overrideImmutable) {
+ final ShortcutInfo disabled = deleteOrDisableWithId(shortcutId, /* disable =*/ true,
+ overrideImmutable);
+
+ if (disabled != null) {
+ if (disabledMessage != null) {
+ disabled.setDisabledMessage(disabledMessage);
+ } else if (disabledMessageResId != 0) {
+ disabled.setDisabledMessageResId(disabledMessageResId);
+ }
+ }
+ }
+
+ @Nullable
+ private ShortcutInfo deleteOrDisableWithId(@NonNull String shortcutId, boolean disable,
+ boolean overrideImmutable) {
final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
- if (oldShortcut == null) {
- return;
+ if (oldShortcut == null || !oldShortcut.isEnabled()) {
+ return null; // Doesn't exist or already disabled.
}
- if (oldShortcut.isDynamic()) {
- mDynamicShortcutCount--;
+ if (!overrideImmutable) {
+ ensureNotImmutable(oldShortcut);
}
if (oldShortcut.isPinned()) {
- oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+ oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST);
+ if (disable) {
+ oldShortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
+ }
+ return oldShortcut;
} else {
- deleteShortcut(shortcutId);
+ deleteShortcutInner(shortcutId);
+ return null;
+ }
+ }
+
+ public void enableWithId(@NonNull String shortcutId) {
+ final ShortcutInfo shortcut = mShortcuts.get(shortcutId);
+ if (shortcut != null) {
+ ensureNotImmutable(shortcut);
+ shortcut.clearFlags(ShortcutInfo.FLAG_DISABLED);
}
}
@@ -430,22 +497,19 @@
for (int i = 0; i < mShortcuts.size(); i++) {
final ShortcutInfo si = mShortcuts.valueAt(i);
- // If it's called by non-launcher (i.e. publisher, always include -> true.
- // Otherwise, only include non-dynamic pinned one, if the calling launcher has pinned
- // it.
+ // Need to adjust PINNED flag depending on the caller.
+ // Basically if the caller is a launcher (callingLauncher != null) and the launcher
+ // isn't pinning it, then we need to clear PINNED for this caller.
final boolean isPinnedByCaller = (callingLauncher == null)
|| ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId()));
- if (!si.isDynamic()) {
- if (!si.isPinned()) {
- s.wtf("Shortcut not pinned: package " + getPackageName()
- + ", user=" + getPackageUserId() + ", id=" + si.getId());
- continue;
- }
+
+ if (si.isFloating()) {
if (!isPinnedByCaller) {
continue;
}
}
final ShortcutInfo clone = si.clone(cloneFlag);
+
// Fix up isPinned for the caller. Note we need to do it before the "test" callback,
// since it may check isPinned.
if (!isPinnedByCaller) {
@@ -486,32 +550,81 @@
}
/**
- * Called when the package is updated. If there are shortcuts with resource icons, update
- * their timestamps.
+ * Called when the package is updated or added.
+ *
+ * Add case:
+ * - Publish manifest shortcuts.
+ *
+ * Update case:
+ * - Re-publish manifest shortcuts.
+ * - If there are shortcuts with resources (icons or strings), update their timestamps.
+ *
+ * @return TRUE if any shortcuts have been changed.
*/
- public void handlePackageUpdated(int newVersionCode) {
- if (getPackageInfo().getVersionCode() >= newVersionCode) {
- // Version hasn't changed; nothing to do.
- return;
- }
- if (ShortcutService.DEBUG) {
- Slog.d(TAG, String.format("Package %s updated, version %d -> %d", getPackageName(),
- getPackageInfo().getVersionCode(), newVersionCode));
+ public boolean handlePackageAddedOrUpdated(boolean isNewApp) {
+ final PackageInfo pi = mShortcutUser.mService.getPackageInfo(
+ getPackageName(), getPackageUserId());
+ if (pi == null) {
+ return false; // Shouldn't happen.
}
- getPackageInfo().setVersionCode(newVersionCode);
+ if (!isNewApp) {
+ // Make sure the version code or last update time has changed.
+ // Otherwise, nothing to do.
+ if (getPackageInfo().getVersionCode() >= pi.versionCode
+ && getPackageInfo().getLastUpdateTime() >= pi.lastUpdateTime) {
+ return false;
+ }
+ }
+
+ // Now prepare to publish manifest shortcuts.
+ List<ShortcutInfo> newManifestShortcutList = null;
+ try {
+ newManifestShortcutList = ShortcutParser.parseShortcuts(mShortcutUser.mService,
+ getPackageName(), getPackageUserId());
+ } catch (IOException|XmlPullParserException e) {
+ Slog.e(TAG, "Failed to load shortcuts from AndroidManifest.xml.", e);
+ }
+ final int manifestShortcutSize = newManifestShortcutList == null ? 0
+ : newManifestShortcutList.size();
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, String.format("Package %s has %d manifest shortcut(s)",
+ getPackageName(), manifestShortcutSize));
+ }
+ if (isNewApp && (manifestShortcutSize == 0)) {
+ // If it's a new app, and it doesn't have manifest shortcuts, then nothing to do.
+
+ // If it's an update, then it may already have manifest shortcuts, which need to be
+ // disabled.
+ return false;
+ }
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, String.format("Package %s %s, version %d -> %d", getPackageName(),
+ (isNewApp ? "added" : "updated"),
+ getPackageInfo().getVersionCode(), pi.versionCode));
+ }
+
+ getPackageInfo().updateVersionInfo(pi);
final ShortcutService s = mShortcutUser.mService;
boolean changed = false;
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
- if (si.hasAnyResources()) {
- changed = true;
- si.setTimestamp(s.injectCurrentTimeMillis());
+ // For existing shortcuts, update timestamps if they have any resources.
+ if (!isNewApp) {
+ for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+ final ShortcutInfo si = mShortcuts.valueAt(i);
+
+ if (si.hasAnyResources()) {
+ changed = true;
+ si.setTimestamp(s.injectCurrentTimeMillis());
+ }
}
}
+
+ // (Re-)publish manifest shortcut.
+ changed |= publishManifestShortcuts(newManifestShortcutList);
+
if (changed) {
// This will send a notification to the launcher, and also save .
s.packageShortcutsChanged(getPackageName(), getPackageUserId());
@@ -519,6 +632,97 @@
// Still save the version code.
s.scheduleSaveUser(getPackageUserId());
}
+ return changed;
+ }
+
+ private boolean publishManifestShortcuts(List<ShortcutInfo> newManifestShortcutList) {
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, String.format(
+ "Package %s: publishing manifest shortcuts", getPackageName()));
+ }
+ boolean changed = false;
+
+ // TODO: Check dynamic count
+
+ // TODO: Kick out dynamic if too many
+
+ // Keep the previous IDs.
+ ArraySet<String> toDisableList = null;
+ for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+ final ShortcutInfo si = mShortcuts.valueAt(i);
+
+ if (si.isManifestShortcut()) {
+ if (toDisableList == null) {
+ toDisableList = new ArraySet<>();
+ }
+ toDisableList.add(si.getId());
+ }
+ }
+
+ // Publish new ones.
+ if (newManifestShortcutList != null) {
+ final int newListSize = newManifestShortcutList.size();
+
+ for (int i = 0; i < newListSize; i++) {
+ changed = true;
+
+ final ShortcutInfo newShortcut = newManifestShortcutList.get(i);
+ final boolean newDisabled = !newShortcut.isEnabled();
+
+ final String id = newShortcut.getId();
+ final ShortcutInfo oldShortcut = mShortcuts.get(id);
+
+ boolean wasPinned = false;
+
+ if (oldShortcut != null) {
+ if (!oldShortcut.isOriginallyFromManifest()) {
+ Slog.e(TAG, "Shortcut with ID=" + newShortcut.getId()
+ + " exists but is not from AndroidManifest.xml, not updating.");
+ continue;
+ }
+ // Take over the pinned flag.
+ if (oldShortcut.isPinned()) {
+ wasPinned = true;
+ newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
+ }
+ }
+ if (newDisabled && !wasPinned) {
+ // If the shortcut is disabled, and it was *not* pinned, then this
+ // just doesn't have to be published.
+ // Just keep it in toDisableList, so the previous one would be removed.
+ continue;
+ }
+ // TODO: Check dynamic count
+
+ // Note even if enabled=false, we still need to update all fields, so do it
+ // regardless.
+ addShortcutInner(newShortcut); // This will clean up the old one too.
+
+ if (!newDisabled && toDisableList != null) {
+ // Still alive, don't remove.
+ toDisableList.remove(id);
+ }
+ }
+ }
+
+ // Disable the previous manifest shortcuts that are no longer in the manifest.
+ if (toDisableList != null) {
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, String.format(
+ "Package %s: disabling %d stale shortcuts", getPackageName(),
+ toDisableList.size()));
+ }
+ for (int i = toDisableList.size() - 1; i >= 0; i--) {
+ changed = true;
+
+ final String id = toDisableList.valueAt(i);
+
+ disableWithId(id, /* disable message =*/ null, /* disable message resid */ 0,
+ /* overrideImmutable=*/ true);
+ }
+ removeOrphans();
+ }
+ return changed;
}
public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
@@ -597,7 +801,6 @@
out.startTag(null, TAG_ROOT);
ShortcutService.writeAttr(out, ATTR_NAME, getPackageName());
- ShortcutService.writeAttr(out, ATTR_DYNAMIC_COUNT, mDynamicShortcutCount);
ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
getPackageInfo().saveToXml(out);
@@ -619,7 +822,7 @@
out.startTag(null, TAG_SHORTCUT);
ShortcutService.writeAttr(out, ATTR_ID, si.getId());
// writeAttr(out, "package", si.getPackageName()); // not needed
- ShortcutService.writeAttr(out, ATTR_ACTIVITY, si.getActivityComponent());
+ ShortcutService.writeAttr(out, ATTR_ACTIVITY, si.getActivity());
// writeAttr(out, "icon", si.getIcon()); // We don't save it.
ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle());
ShortcutService.writeAttr(out, ATTR_TITLE_RES_ID, si.getTitleResId());
@@ -670,8 +873,6 @@
final ShortcutPackage ret = new ShortcutPackage(shortcutUser,
shortcutUser.getUserId(), packageName);
- ret.mDynamicShortcutCount =
- ShortcutService.parseIntAttribute(parser, ATTR_DYNAMIC_COUNT);
ret.mApiCallCount =
ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
ret.mLastResetTime =
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
index ae9709e..7f5d931 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
@@ -15,6 +15,7 @@
*/
package com.android.server.pm;
+import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.pm.PackageInfo;
import android.util.Slog;
@@ -34,12 +35,15 @@
/**
* Package information used by {@link android.content.pm.ShortcutManager} for backup / restore.
+ *
+ * All methods should be guarded by {@code ShortcutService.mLock}.
*/
class ShortcutPackageInfo {
private static final String TAG = ShortcutService.TAG;
static final String TAG_ROOT = "package-info";
private static final String ATTR_VERSION = "version";
+ private static final String ATTR_LAST_UPDATE_TIME = "last_udpate_time";
private static final String ATTR_SHADOW = "shadow";
private static final String TAG_SIGNATURE = "signature";
@@ -53,16 +57,20 @@
*/
private boolean mIsShadow;
private int mVersionCode = VERSION_UNKNOWN;
+ private long mLastUpdateTime;
private ArrayList<byte[]> mSigHashes;
- private ShortcutPackageInfo(int versionCode, ArrayList<byte[]> sigHashes, boolean isShadow) {
+ private ShortcutPackageInfo(int versionCode, long lastUpdateTime,
+ ArrayList<byte[]> sigHashes, boolean isShadow) {
mVersionCode = versionCode;
+ mLastUpdateTime = lastUpdateTime;
mIsShadow = isShadow;
mSigHashes = sigHashes;
}
public static ShortcutPackageInfo newEmpty() {
- return new ShortcutPackageInfo(VERSION_UNKNOWN, new ArrayList<>(0), /* isShadow */ false);
+ return new ShortcutPackageInfo(VERSION_UNKNOWN, /* last update time =*/ 0,
+ new ArrayList<>(0), /* isShadow */ false);
}
public boolean isShadow() {
@@ -77,8 +85,15 @@
return mVersionCode;
}
- public void setVersionCode(int versionCode) {
- mVersionCode = versionCode;
+ public long getLastUpdateTime() {
+ return mLastUpdateTime;
+ }
+
+ public void updateVersionInfo(@NonNull PackageInfo pi) {
+ if (pi != null) {
+ mVersionCode = pi.versionCode;
+ mLastUpdateTime = pi.lastUpdateTime;
+ }
}
public boolean hasSignatures() {
@@ -111,7 +126,7 @@
Slog.e(TAG, "Can't get signatures: package=" + packageName);
return null;
}
- final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.versionCode,
+ final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.versionCode, pi.lastUpdateTime,
BackupUtils.hashSignatureArray(pi.signatures), /* shadow=*/ false);
return ret;
@@ -131,6 +146,7 @@
return;
}
mVersionCode = pi.versionCode;
+ mLastUpdateTime = pi.lastUpdateTime;
mSigHashes = BackupUtils.hashSignatureArray(pi.signatures);
}
@@ -139,6 +155,7 @@
out.startTag(null, TAG_ROOT);
ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode);
+ ShortcutService.writeAttr(out, ATTR_LAST_UPDATE_TIME, mLastUpdateTime);
ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow);
for (int i = 0; i < mSigHashes.size(); i++) {
@@ -154,6 +171,9 @@
final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION);
+ final long lastUpdateTime = ShortcutService.parseIntAttribute(
+ parser, ATTR_LAST_UPDATE_TIME);
+
// When restoring from backup, it's always shadow.
final boolean shadow =
fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
@@ -185,6 +205,7 @@
// Successfully loaded; replace the feilds.
mVersionCode = versionCode;
+ mLastUpdateTime = lastUpdateTime;
mIsShadow = shadow;
mSigHashes = hashes;
}
@@ -205,6 +226,11 @@
pw.print(mVersionCode);
pw.println();
+ pw.print(prefix);
+ pw.print(" Last package update time: ");
+ pw.print(mLastUpdateTime);
+ pw.println();
+
for (int i = 0; i < mSigHashes.size(); i++) {
pw.print(prefix);
pw.print(" ");
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index 0c2417c..d08b974 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -26,6 +26,9 @@
import java.io.IOException;
+/**
+ * All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
+ */
abstract class ShortcutPackageItem {
private static final String TAG = ShortcutService.TAG;
diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java
new file mode 100644
index 0000000..44d0a9f
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ShortcutParser.java
@@ -0,0 +1,236 @@
+/*
+ * 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.
+ */
+package com.android.server.pm;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+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.Slog;
+import android.util.Xml;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class ShortcutParser {
+ private static final String TAG = ShortcutService.TAG;
+
+ private static final boolean DEBUG = ShortcutService.DEBUG || false; // DO NOT SUBMIT WITH TRUE
+
+ @VisibleForTesting
+ static final String METADATA_KEY = "android.pm.Shortcuts";
+
+ private static final String TAG_SHORTCUTS = "shortcuts";
+ private static final String TAG_SHORTCUT = "shortcut";
+
+ @Nullable
+ public static List<ShortcutInfo> parseShortcuts(ShortcutService service,
+ String packageName, @UserIdInt int userId) throws IOException, XmlPullParserException {
+ final PackageInfo pi = service.injectGetActivitiesWithMetadata(packageName, userId);
+
+ List<ShortcutInfo> result = null;
+
+ if (pi != null && pi.activities != null) {
+ for (ActivityInfo activityInfo : pi.activities) {
+ result = parseShortcutsOneFile(service, activityInfo, packageName, userId, result);
+ }
+ }
+ return result;
+ }
+
+ private static List<ShortcutInfo> parseShortcutsOneFile(
+ ShortcutService service,
+ ActivityInfo activityInfo, String packageName, @UserIdInt int userId,
+ List<ShortcutInfo> result) throws IOException, XmlPullParserException {
+ XmlResourceParser parser = null;
+ try {
+ parser = service.injectXmlMetaData(activityInfo, METADATA_KEY);
+ if (parser == null) {
+ return result;
+ }
+
+ final ComponentName activity = new ComponentName(packageName, activityInfo.name);
+
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+
+ outer:
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > 0)) {
+ 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.
+ }
+ if (depth == 2 && TAG_SHORTCUT.equals(tag)) {
+ final ShortcutInfo si = parseShortcutAttributes(
+ service, attrs, packageName, activity, userId);
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, "Shortcut=" + si);
+ }
+ 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.");
+ continue outer;
+ }
+ }
+ }
+
+ if (si != null) {
+ if (result == null) {
+ result = new ArrayList<>();
+ }
+ result.add(si);
+ }
+ continue;
+ }
+ Slog.w(TAG, "Unknown tag " + tag);
+ }
+ } finally {
+ if (parser != null) {
+ parser.close();
+ }
+ }
+ return result;
+ }
+
+ private static ShortcutInfo parseShortcutAttributes(ShortcutService service,
+ AttributeSet attrs, String packageName, ComponentName activity,
+ @UserIdInt int userId) {
+ final TypedArray sa = service.mContext.getResources().obtainAttributes(attrs,
+ R.styleable.Shortcut);
+ try {
+ final String id = sa.getString(R.styleable.Shortcut_shortcutId);
+ final boolean enabled = sa.getBoolean(R.styleable.Shortcut_enabled, true);
+ final int rank = sa.getInt(R.styleable.Shortcut_shortcutRank, 0);
+ final int iconResId = sa.getResourceId(R.styleable.Shortcut_shortcutIcon, 0);
+ final int titleResId = sa.getResourceId(R.styleable.Shortcut_shortcutTitle, 0);
+ final int textResId = sa.getResourceId(R.styleable.Shortcut_shortcutText, 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);
+ return null;
+ }
+ if (titleResId == 0) {
+ 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,
+ userId,
+ id,
+ packageName,
+ activity,
+ titleResId,
+ textResId,
+ disabledMessageResId,
+ categoriesSet,
+ intent,
+ rank,
+ iconResId,
+ enabled);
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ 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) {
+
+ final int flags =
+ (enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED)
+ | ShortcutInfo.FLAG_IMMUTABLE
+ | ((iconResId != 0) ? ShortcutInfo.FLAG_HAS_ICON_RES : 0);
+
+ return new ShortcutInfo(
+ userId,
+ id,
+ packageName,
+ activityComponent,
+ null, // icon
+ null, // title string
+ titleResId,
+ null, // text string
+ textResId,
+ null, // disabled message string
+ disabledMessageResId,
+ categories,
+ intent,
+ null, // intent extras
+ rank,
+ null, // extras
+ service.injectCurrentTimeMillis(),
+ flags,
+ iconResId,
+ null); // bitmap path
+ }
+}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 6a10f41..dc0404c 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -25,6 +25,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.IShortcutService;
@@ -39,6 +40,7 @@
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
+import android.content.res.XmlResourceParser;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Canvas;
@@ -103,6 +105,7 @@
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
@@ -111,16 +114,21 @@
/**
* TODO:
- * - Manifest shortcuts.
+ * - Implement # of dynamic shortcuts cap.
*
- * - Implement disableShortcuts().
+ * - HandleUnlockUser needs to be async. Wait on it in onCleanupUser.
*
* - Implement reportShortcutUsed().
*
+ * - validateForXml() should be removed.
+ *
* - Ranks should be recalculated after each update.
*
* - When the system locale changes, update timestamps for shortcuts with string resources,
- * and notify the launcher.
+ * and notify the launcher. Right now, it resets the throttling, but timestamps are not changed
+ * and there's no notification either.
+ *
+ * - getIconMaxWidth()/getIconMaxHeight() should use xdpi and ydpi.
*
* - Default launcher check does take a few ms. Worth caching.
*
@@ -128,11 +136,13 @@
* internal bitmap handling.
*
* - Add more call stats.
+ *
+ * - Rename getMaxDynamicShortcutCount and mMaxDynamicShortcuts
*/
public class ShortcutService extends IShortcutService.Stub {
static final String TAG = "ShortcutService";
- static final boolean DEBUG = false; // STOPSHIP if true
+ static final boolean DEBUG = true; // STOPSHIP if true
static final boolean DEBUG_LOAD = false; // STOPSHIP if true
static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
@@ -301,8 +311,11 @@
int GET_APPLICATION_INFO = 3;
int LAUNCHER_PERMISSION_CHECK = 4;
int CLEANUP_DANGLING_BITMAPS = 5;
+ int GET_ACTIVITIES_WITH_METADATA = 6;
+ int GET_INSTALLED_APPLICATIONS = 7;
+ int CHECK_PACKAGE_CHANGES = 8;
- int COUNT = CLEANUP_DANGLING_BITMAPS + 1;
+ int COUNT = CHECK_PACKAGE_CHANGES + 1;
}
final Object mStatLock = new Object();
@@ -444,6 +457,9 @@
/** lifecycle event */
void handleUnlockUser(int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "handleUnlockUser: user=" + userId);
+ }
synchronized (mLock) {
// Preload
getUserShortcutsLocked(userId);
@@ -642,10 +658,10 @@
out.endTag(null, tag);
}
- static void writeAttr(XmlSerializer out, String name, String value) throws IOException {
+ static void writeAttr(XmlSerializer out, String name, CharSequence value) throws IOException {
if (TextUtils.isEmpty(value)) return;
- out.attribute(null, name, value);
+ out.attribute(null, name, value.toString());
}
static void writeAttr(XmlSerializer out, String name, long value) throws IOException {
@@ -1090,7 +1106,7 @@
FileOutputStreamWithPath openIconFileForWrite(@UserIdInt int userId, ShortcutInfo shortcut)
throws IOException {
final File packagePath = new File(getUserBitmapFilePath(userId),
- shortcut.getPackageName());
+ shortcut.getPackage());
if (!packagePath.isDirectory()) {
packagePath.mkdirs();
if (!packagePath.isDirectory()) {
@@ -1129,7 +1145,6 @@
}
Bitmap bitmap;
- Bitmap bitmapToRecycle = null;
try {
switch (icon.getType()) {
case Icon.TYPE_RESOURCE: {
@@ -1181,9 +1196,6 @@
}
}
} finally {
- if (bitmapToRecycle != null) {
- bitmapToRecycle.recycle();
- }
// Once saved, we won't use the original icon information, so null it out.
shortcut.clearIcon();
}
@@ -1196,7 +1208,7 @@
// so override in unit tests.
// TODO CTS this case.
void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) {
- if (!shortcut.getPackageName().equals(icon.getResPackage())) {
+ if (!shortcut.getPackage().equals(icon.getResPackage())) {
throw new IllegalArgumentException(
"Icon resource must reside in shortcut owner package");
}
@@ -1353,10 +1365,9 @@
*/
private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) {
Preconditions.checkNotNull(shortcut, "Null shortcut detected");
- if (shortcut.getActivityComponent() != null) {
+ if (shortcut.getActivity() != null) {
Preconditions.checkState(
- shortcut.getPackageName().equals(
- shortcut.getActivityComponent().getPackageName()),
+ shortcut.getPackage().equals(shortcut.getActivity().getPackageName()),
"Activity package name mismatch");
}
@@ -1399,7 +1410,7 @@
}
}
- private static void validateForXml(String s) {
+ private static void validateForXml(CharSequence s) {
if (TextUtils.isEmpty(s)) {
return;
}
@@ -1427,6 +1438,8 @@
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+ ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
+
// Throttling.
if (!ps.tryApiCall()) {
return false;
@@ -1444,7 +1457,7 @@
// Then, add/update all. We need to make sure to take over "pinned" flag.
for (int i = 0; i < size; i++) {
final ShortcutInfo newShortcut = newShortcuts.get(i);
- ps.addDynamicShortcut(newShortcut);
+ ps.addOrUpdateDynamicShortcut(newShortcut);
}
}
packageShortcutsChanged(packageName, userId);
@@ -1462,6 +1475,8 @@
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+ ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
+
// Throttling.
if (!ps.tryApiCall()) {
return false;
@@ -1473,11 +1488,22 @@
final ShortcutInfo target = ps.findShortcutById(source.getId());
if (target != null) {
+ if (target.isEnabled() != source.isEnabled()) {
+ Slog.w(TAG,
+ "ShortcutInfo.enabled cannot be changed with updateShortcuts()");
+ }
+
final boolean replacingIcon = (source.getIcon() != null);
if (replacingIcon) {
removeIcon(userId, target);
}
+ if (source.getActivity() != null &&
+ !source.getActivity().equals(target.getActivity())) {
+ // TODO When activity is changing, check the dynamic count.
+ }
+
+ // Note copyNonNullFieldsFrom() does the "udpatable with?" check too.
target.copyNonNullFieldsFrom(source);
if (replacingIcon) {
@@ -1502,6 +1528,8 @@
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+ ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
+
// Throttling.
if (!ps.tryApiCall()) {
return false;
@@ -1513,7 +1541,7 @@
fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false);
// Add it.
- ps.addDynamicShortcut(newShortcut);
+ ps.addOrUpdateDynamicShortcut(newShortcut);
}
}
packageShortcutsChanged(packageName, userId);
@@ -1528,7 +1556,32 @@
Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
synchronized (mLock) {
- // TODO implement it.
+ final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+
+ ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
+
+ for (int i = shortcutIds.size() - 1; i >= 0; i--) {
+ ps.disableWithId(Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)),
+ disabledMessage, disabledMessageResId,
+ /* overrideImmutable=*/ false);
+ }
+ }
+ packageShortcutsChanged(packageName, userId);
+ }
+
+ @Override
+ public void enableShortcuts(String packageName, List shortcutIds, @UserIdInt int userId) {
+ verifyCaller(packageName, userId);
+ Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
+
+ synchronized (mLock) {
+ final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+
+ ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
+
+ for (int i = shortcutIds.size() - 1; i >= 0; i--) {
+ ps.enableWithId((String) shortcutIds.get(i));
+ }
}
packageShortcutsChanged(packageName, userId);
}
@@ -1540,8 +1593,12 @@
Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
synchronized (mLock) {
+ final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+
+ ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
+
for (int i = shortcutIds.size() - 1; i >= 0; i--) {
- getPackageShortcutsLocked(packageName, userId).deleteDynamicWithId(
+ ps.deleteDynamicWithId(
Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)));
}
}
@@ -1570,6 +1627,17 @@
}
@Override
+ public ParceledListSlice<ShortcutInfo> getManifestShortcuts(String packageName,
+ @UserIdInt int userId) {
+ verifyCaller(packageName, userId);
+ synchronized (mLock) {
+ return getShortcutsWithQueryLocked(
+ packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
+ ShortcutInfo::isManifestShortcut);
+ }
+ }
+
+ @Override
public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName,
@UserIdInt int userId) {
verifyCaller(packageName, userId);
@@ -1883,18 +1951,24 @@
return false;
}
if (componentName != null) {
- if (si.getActivityComponent() != null
- && !si.getActivityComponent().equals(componentName)) {
+ if (si.getActivity() != null
+ && !si.getActivity().equals(componentName)) {
return false;
}
}
- final boolean matchDynamic =
- ((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0)
- && si.isDynamic();
- final boolean matchPinned =
- ((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0)
- && si.isPinned();
- return matchDynamic || matchPinned;
+ if (((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0)
+ && si.isDynamic()) {
+ return true;
+ }
+ if (((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0)
+ && si.isPinned()) {
+ return true;
+ }
+ if (((queryFlags & ShortcutQuery.FLAG_GET_MANIFEST) != 0)
+ && si.isManifestShortcut()) {
+ return true;
+ }
+ return false;
}, cloneFlag, callingPackage, launcherUserId);
}
@@ -1967,7 +2041,7 @@
final ShortcutInfo si = getShortcutInfoLocked(
launcherUserId, callingPackage, packageName, shortcutId, userId);
// "si == null" should suffice here, but check the flags too just to make sure.
- if (si == null || !(si.isDynamic() || si.isPinned())) {
+ if (si == null || !si.isEnabled() || !si.isAlive()) {
return null;
}
return si.getIntent();
@@ -2114,31 +2188,37 @@
if (DEBUG) {
Slog.d(TAG, "checkPackageChanges() ownerUserId=" + ownerUserId);
}
- final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
- synchronized (mLock) {
- final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
+ final long start = injectElapsedRealtime();
+ try {
+ final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
- user.forAllPackageItems(spi -> {
- if (spi.getPackageInfo().isShadow()) {
- return; // Don't delete shadow information.
+ synchronized (mLock) {
+ final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
+
+ // Find packages that have been uninstalled.
+ user.forAllPackageItems(spi -> {
+ if (spi.getPackageInfo().isShadow()) {
+ return; // Don't delete shadow information.
+ }
+ if (!isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) {
+ gonePackages.add(PackageWithUser.of(spi));
+ }
+ });
+ if (gonePackages.size() > 0) {
+ for (int i = gonePackages.size() - 1; i >= 0; i--) {
+ final PackageWithUser pu = gonePackages.get(i);
+ cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId);
+ }
}
- final int versionCode = getApplicationVersionCode(
- spi.getPackageName(), spi.getPackageUserId());
- if (versionCode >= 0) {
- // Package still installed, see if it's updated.
- getUserShortcutsLocked(ownerUserId).handlePackageUpdated(
- spi.getPackageName(), versionCode);
- } else {
- gonePackages.add(PackageWithUser.of(spi));
- }
- });
- if (gonePackages.size() > 0) {
- for (int i = gonePackages.size() - 1; i >= 0; i--) {
- final PackageWithUser pu = gonePackages.get(i);
- cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId);
- }
+
+ // Then for each installed app, publish manifest shortcuts when needed.
+ forInstalledApplications(ownerUserId, ai -> {
+ user.handlePackageAddedOrUpdated(ai.packageName);
+ });
}
+ } finally {
+ logDurationStat(Stats.CHECK_PACKAGE_CHANGES, start);
}
}
@@ -2147,8 +2227,9 @@
Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
}
synchronized (mLock) {
- forEachLoadedUserLocked(user ->
- user.attemptToRestoreIfNeededAndSave(this, packageName, userId));
+ final ShortcutUser user = getUserShortcutsLocked(userId);
+ user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
+ user.handlePackageAddedOrUpdated(packageName);
}
}
@@ -2158,14 +2239,12 @@
packageName, userId));
}
synchronized (mLock) {
- forEachLoadedUserLocked(user ->
- user.attemptToRestoreIfNeededAndSave(this, packageName, userId));
+ final ShortcutUser user = getUserShortcutsLocked(userId);
+ user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
- final int versionCode = getApplicationVersionCode(packageName, userId);
- if (versionCode < 0) {
- return; // shouldn't happen
+ if (isPackageInstalled(packageName, userId)) {
+ user.handlePackageAddedOrUpdated(packageName);
}
- getUserShortcutsLocked(userId).handlePackageUpdated(packageName, versionCode);
}
}
@@ -2187,10 +2266,16 @@
// === PackageManager interaction ===
+ @Nullable
PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) {
return injectPackageInfo(packageName, userId, true);
}
+ @Nullable
+ PackageInfo getPackageInfo(String packageName, @UserIdInt int userId) {
+ return injectPackageInfo(packageName, userId, false);
+ }
+
int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) {
final long token = injectClearCallingIdentity();
try {
@@ -2205,6 +2290,7 @@
}
}
+ @Nullable
@VisibleForTesting
PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
boolean getSignatures) {
@@ -2227,6 +2313,7 @@
}
}
+ @Nullable
@VisibleForTesting
ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
final long start = injectElapsedRealtime();
@@ -2244,6 +2331,61 @@
}
}
+ @Nullable
+ @VisibleForTesting
+ PackageInfo injectGetActivitiesWithMetadata(String packageName, @UserIdInt int userId) {
+ final long start = injectElapsedRealtime();
+ final long token = injectClearCallingIdentity();
+ try {
+ return mIPackageManager.getPackageInfo(packageName,
+ PACKAGE_MATCH_FLAGS | PackageManager.GET_ACTIVITIES
+ | PackageManager.GET_META_DATA, userId);
+ } catch (RemoteException e) {
+ // Shouldn't happen.
+ Slog.wtf(TAG, "RemoteException", e);
+ return null;
+ } finally {
+ injectRestoreCallingIdentity(token);
+
+ logDurationStat(Stats.GET_ACTIVITIES_WITH_METADATA, start);
+ }
+ }
+
+ @Nullable
+ @VisibleForTesting
+ List<ApplicationInfo> injectInstalledApplications(@UserIdInt int userId) {
+ final long start = injectElapsedRealtime();
+ final long token = injectClearCallingIdentity();
+ try {
+ final ParceledListSlice<ApplicationInfo> parceledList =
+ mIPackageManager.getInstalledApplications(PACKAGE_MATCH_FLAGS, userId);
+ if (parceledList == null) {
+ return Collections.emptyList();
+ }
+ return parceledList.getList();
+ } catch (RemoteException e) {
+ // Shouldn't happen.
+ Slog.wtf(TAG, "RemoteException", e);
+ return null;
+ } finally {
+ injectRestoreCallingIdentity(token);
+
+ logDurationStat(Stats.GET_INSTALLED_APPLICATIONS, start);
+ }
+ }
+
+ private void forInstalledApplications(@UserIdInt int userId,
+ Consumer<ApplicationInfo> callback) {
+ final List<ApplicationInfo> list = injectInstalledApplications(userId);
+ for (int i = list.size() - 1; i >= 0; i--) {
+ final ApplicationInfo ai = list.get(i);
+
+ if ((ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
+ callback.accept(ai);
+ }
+ }
+ }
+
private boolean isApplicationFlagSet(String packageName, int userId, int flags) {
final ApplicationInfo ai = injectApplicationInfo(packageName, userId);
return (ai != null) && ((ai.flags & flags) == flags);
@@ -2253,15 +2395,10 @@
return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED);
}
- /**
- * @return the version code of the package, or -1 if the app is not installed.
- */
- int getApplicationVersionCode(String packageName, int userId) {
- final ApplicationInfo ai = injectApplicationInfo(packageName, userId);
- if ((ai == null) || ((ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) {
- return -1;
- }
- return ai.versionCode;
+ @Nullable
+ XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) {
+// TODO Doesn't seem like ACROSS_USER is needed, but double check.
+ return activityInfo.loadXmlMetaData(mContext.getPackageManager(), key);
}
// === Backup & restore ===
@@ -2402,8 +2539,10 @@
dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO, "getPackageInfo()");
dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO_WITH_SIG, "getPackageInfo(SIG)");
dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo");
-
dumpStatLS(pw, p, Stats.CLEANUP_DANGLING_BITMAPS, "cleanupDanglingBitmaps");
+ dumpStatLS(pw, p, Stats.GET_ACTIVITIES_WITH_METADATA, "getActivities+metadata");
+ dumpStatLS(pw, p, Stats.GET_INSTALLED_APPLICATIONS, "getInstalledApplications");
+ dumpStatLS(pw, p, Stats.CHECK_PACKAGE_CHANGES, "checkPackageChanges");
}
for (int i = 0; i < mUsers.size(); i++) {
@@ -2783,12 +2922,19 @@
}
@VisibleForTesting
- ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) {
+ ShortcutPackage getPackageShortcutForTest(String packageName, int userId) {
synchronized (mLock) {
final ShortcutUser user = mUsers.get(userId);
if (user == null) return null;
- final ShortcutPackage pkg = user.getAllPackagesForTest().get(packageName);
+ return user.getAllPackagesForTest().get(packageName);
+ }
+ }
+
+ @VisibleForTesting
+ ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) {
+ synchronized (mLock) {
+ final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId);
if (pkg == null) return null;
return pkg.findShortcutById(shortcutId);
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index d38cfba..840c3df 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -24,6 +24,7 @@
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
@@ -40,6 +41,8 @@
/**
* User information used by {@link ShortcutService}.
+ *
+ * All methods should be guarded by {@code #mService.mLock}.
*/
class ShortcutUser {
private static final String TAG = ShortcutService.TAG;
@@ -230,19 +233,16 @@
}
}
- /**
- * Called when a package is updated.
- */
- public void handlePackageUpdated(@NonNull String packageName,
- int newVersionCode) {
- if (!mPackages.containsKey(packageName)) {
- return;
+ public void handlePackageAddedOrUpdated(@NonNull String packageName) {
+ final boolean isNewApp = !mPackages.containsKey(packageName);
+
+ final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName);
+
+ if (!shortcutPackage.handlePackageAddedOrUpdated(isNewApp)) {
+ if (isNewApp) {
+ mPackages.remove(packageName);
+ }
}
- final ShortcutPackage p = getPackageShortcutsIfExists(packageName);
- if (p == null) {
- return; // No need to instantiate ShortcutPackage.
- }
- p.handlePackageUpdated(newVersionCode);
}
public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,
diff --git a/services/tests/servicestests/res/values/strings.xml b/services/tests/servicestests/res/values/strings.xml
new file mode 100644
index 0000000..2f9d06c
--- /dev/null
+++ b/services/tests/servicestests/res/values/strings.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="shortcut_title1"></string>
+ <string name="shortcut_text1"></string>
+ <string name="shortcut_disabled_message1"></string>
+ <string name="shortcut_title2"></string>
+ <string name="shortcut_text2"></string>
+ <string name="shortcut_disabled_message2"></string>
+</resources>
diff --git a/services/tests/servicestests/res/xml/shortcut_0.xml b/services/tests/servicestests/res/xml/shortcut_0.xml
new file mode 100644
index 0000000..fda001e
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_0.xml
@@ -0,0 +1,2 @@
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_1.xml b/services/tests/servicestests/res/xml/shortcut_1.xml
new file mode 100644
index 0000000..c370e74
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_1.xml
@@ -0,0 +1,14 @@
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <shortcut
+ android:shortcutId="ms1"
+ android:enabled="true"
+ android:shortcutRank="1"
+ android:shortcutIcon="@drawable/icon1"
+ android:shortcutTitle="@string/shortcut_title1"
+ android:shortcutText="@string/shortcut_text1"
+ android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
+ android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
+ android:shortcutIntentAction="action1"
+ android:shortcutIntentData="data1"
+ />
+</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_1_disable.xml b/services/tests/servicestests/res/xml/shortcut_1_disable.xml
new file mode 100644
index 0000000..08ecac3
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_1_disable.xml
@@ -0,0 +1,11 @@
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <shortcut
+ android:shortcutId="ms1"
+ android:enabled="false"
+ android:shortcutRank="1"
+ android:shortcutIcon="@drawable/icon2"
+ android:shortcutTitle="@string/shortcut_title2"
+ android:shortcutText="@string/shortcut_text2"
+ android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
+ />
+</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_2.xml b/services/tests/servicestests/res/xml/shortcut_2.xml
new file mode 100644
index 0000000..9e923f3
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_2.xml
@@ -0,0 +1,26 @@
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <shortcut
+ android:shortcutId="ms1"
+ android:enabled="true"
+ android:shortcutRank="1"
+ android:shortcutIcon="@drawable/icon1"
+ android:shortcutTitle="@string/shortcut_title1"
+ android:shortcutText="@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/"
+ />
+ <shortcut
+ android:shortcutId="ms2"
+ android:enabled="true"
+ android:shortcutRank="2"
+ android:shortcutIcon="@drawable/icon2"
+ android:shortcutTitle="@string/shortcut_title2"
+ android:shortcutText="@string/shortcut_text2"
+ android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
+ android:shortcutCategories="android.shortcut.conversation"
+ android:shortcutIntentAction="action2"
+ android:shortcutIntentData="http://a.b.c/2"
+ />
+</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_2_duplicate.xml b/services/tests/servicestests/res/xml/shortcut_2_duplicate.xml
new file mode 100644
index 0000000..d90c18d
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_2_duplicate.xml
@@ -0,0 +1,12 @@
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <shortcut
+ android:shortcutId="ms1"
+ android:shortcutTitle="@string/shortcut_title1"
+ android:shortcutIntentAction="action1"
+ />
+ <shortcut
+ android:shortcutId="ms1"
+ android:shortcutTitle="@string/shortcut_title2"
+ android:shortcutIntentAction="action2"
+ />
+</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_5.xml b/services/tests/servicestests/res/xml/shortcut_5.xml
new file mode 100644
index 0000000..f3f71d2
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_5.xml
@@ -0,0 +1,40 @@
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <shortcut
+ android:shortcutId="ms1"
+ android:enabled="true"
+ android:shortcutRank="1"
+ android:shortcutIcon="@drawable/icon1"
+ android:shortcutTitle="@string/shortcut_title1"
+ android:shortcutText="@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"
+ />
+ <shortcut
+ android:shortcutId="ms2"
+ android:enabled="true"
+ android:shortcutRank="2"
+ android:shortcutIcon="@drawable/icon2"
+ android:shortcutTitle="@string/shortcut_title2"
+ android:shortcutText="@string/shortcut_text2"
+ android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
+ android:shortcutCategories="android.shortcut.conversation"
+ android:shortcutIntentAction="action2"
+ />
+ <shortcut
+ android:shortcutId="ms3"
+ android:shortcutTitle="@string/shortcut_title1"
+ android:shortcutIntentAction="android.intent.action.VIEW"
+ />
+ <shortcut
+ android:shortcutId="ms4"
+ android:shortcutTitle="@string/shortcut_title1"
+ android:shortcutIntentAction="android.intent.action.VIEW"
+ />
+ <shortcut
+ android:shortcutId="ms5"
+ android:shortcutTitle="@string/shortcut_title1"
+ android:shortcutIntentAction="android.intent.action.VIEW"
+ />
+</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_error_1.xml b/services/tests/servicestests/res/xml/shortcut_error_1.xml
new file mode 100644
index 0000000..2c51420
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_error_1.xml
@@ -0,0 +1,11 @@
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <shortcut
+ android:shortcutTitle="@string/shortcut_title1"
+ android:shortcutIntentAction="android.intent.action.VIEW"
+ />
+ <shortcut
+ android:shortcutId="x1"
+ android:shortcutTitle="@string/shortcut_title1"
+ android:shortcutIntentAction="android.intent.action.VIEW"
+ />
+</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_error_2.xml b/services/tests/servicestests/res/xml/shortcut_error_2.xml
new file mode 100644
index 0000000..d075e7d
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_error_2.xml
@@ -0,0 +1,11 @@
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <shortcut
+ android:shortcutId="manifest-shortcut-3"
+ android:shortcutIntentAction="android.intent.action.VIEW"
+ />
+ <shortcut
+ android:shortcutId="x2"
+ android:shortcutTitle="@string/shortcut_title1"
+ android:shortcutIntentAction="android.intent.action.VIEW"
+ />
+</shortcuts>
diff --git a/services/tests/servicestests/res/xml/shortcut_error_3.xml b/services/tests/servicestests/res/xml/shortcut_error_3.xml
new file mode 100644
index 0000000..30bf56e
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_error_3.xml
@@ -0,0 +1,11 @@
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <shortcut
+ android:shortcutId="manifest-shortcut-3"
+ android:shortcutTitle="@string/shortcut_title1"
+ />
+ <shortcut
+ android:shortcutId="x3"
+ android:shortcutTitle="@string/shortcut_title1"
+ android:shortcutIntentAction="android.intent.action.VIEW"
+ />
+</shortcuts>
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
index 8a2a58c..1a0a003 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
@@ -15,17 +15,22 @@
*/
package com.android.server.pm;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllDisabled;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllDynamic;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllDynamicOrPinned;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllEnabled;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllHaveIcon;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllHaveIconFile;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllHaveIconResId;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllHaveIntents;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllHaveTitle;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllImmutable;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllKeyFieldsOnly;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllManifest;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllNotHaveIntents;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllNotHaveTitle;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllNotKeyFieldsOnly;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllNotManifest;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllPinned;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllStringsResolved;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllUnique;
@@ -33,8 +38,10 @@
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertBundleEmpty;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertCallbackNotReceived;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertCallbackReceived;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertCannotUpdateImmutable;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertDynamicAndPinned;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertDynamicOnly;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertEmpty;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertShortcutIds;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.findShortcut;
@@ -70,6 +77,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ILauncherApps;
import android.content.pm.LauncherApps;
@@ -83,6 +91,7 @@
import android.content.pm.Signature;
import android.content.pm.UserInfo;
import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
@@ -143,6 +152,11 @@
adb shell am instrument -e class com.android.server.pm.ShortcutManagerTest \
-w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+
+ * TODO More tests for pinning + manifest shortcuts
+ * TODO Manifest shortcuts + app upgrade -> launcher callback.
+ * Also locale change should trigger launcehr callbacks too, when they use strign resoucres.
+ * (not implemented yet.)
* TODO: Add checks with assertAllNotHaveIcon()
* TODO: Detailed test for hasShortcutPermissionInner().
* TODO: Add tests for the command line functions too.
@@ -155,9 +169,9 @@
* Whether to enable dump or not. Should be only true when debugging to avoid bugs where
* dump affecting the behavior.
*/
- private static final boolean ENABLE_DUMP = false; // DO NOT SUBMIT WITH true
+ private static final boolean ENABLE_DUMP = true; // DO NOT SUBMIT WITH true
- private static final boolean DUMP_IN_TEARDOWN = false; // DO NOT SUBMIT WITH true
+ private static final boolean DUMP_IN_TEARDOWN = true; // DO NOT SUBMIT WITH true
private static final String[] EMPTY_STRINGS = new String[0]; // Just for readability.
@@ -229,6 +243,15 @@
public int getUserId() {
return UserHandle.USER_SYSTEM;
}
+
+ public PackageInfo injectGetActivitiesWithMetadata(
+ String packageName, @UserIdInt int userId) {
+ return ShortcutManagerTest.this.injectGetActivitiesWithMetadata(packageName, userId);
+ }
+
+ public XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) {
+ return ShortcutManagerTest.this.injectXmlMetaData(activityInfo, key);
+ }
}
/** ShortcutService with injection override methods. */
@@ -339,6 +362,21 @@
}
@Override
+ List<ApplicationInfo> injectInstalledApplications(@UserIdInt int userId) {
+ return getInstalledApplications(userId);
+ }
+
+ @Override
+ PackageInfo injectGetActivitiesWithMetadata(String packageName, @UserIdInt int userId) {
+ return mContext.injectGetActivitiesWithMetadata(packageName, userId);
+ }
+
+ @Override
+ XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) {
+ return mContext.injectXmlMetaData(activityInfo, key);
+ }
+
+ @Override
void postToHandler(Runnable r) {
final long token = mContext.injectClearCallingIdentity();
r.run();
@@ -524,6 +562,9 @@
private final ArrayList<String> mCallerPermissions = new ArrayList<>();
+ private final HashMap<String, HashMap<ComponentName, Integer>> mActivityMetadataResId
+ = new HashMap<>();
+
static {
QUERY_ALL.setQueryFlags(
ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED);
@@ -720,6 +761,12 @@
});
}
+ private void updatePackageLastUpdateTime(String packageName, long increment) {
+ updatePackageInfo(packageName, pi -> {
+ pi.lastUpdateTime += increment;
+ });
+ }
+
private void uninstallPackage(int userId, String packageName) {
if (ENABLE_DUMP) {
Log.i(TAG, "Unnstall package " + packageName + " / " + userId);
@@ -742,8 +789,12 @@
final PackageInfo ret = new PackageInfo();
ret.packageName = pi.packageName;
ret.versionCode = pi.versionCode;
+ ret.lastUpdateTime = pi.lastUpdateTime;
+
ret.applicationInfo = new ApplicationInfo(pi.applicationInfo);
ret.applicationInfo.uid = UserHandle.getUid(userId, pi.applicationInfo.uid);
+ ret.applicationInfo.packageName = pi.packageName;
+
if (mUninstalledPackages.contains(PackageWithUser.of(userId, packageName))) {
ret.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED;
}
@@ -755,6 +806,66 @@
return ret;
}
+ private void addApplicationInfo(PackageInfo pi, List<ApplicationInfo> list) {
+ if (pi != null && pi.applicationInfo != null) {
+ list.add(pi.applicationInfo);
+ }
+ }
+
+ private List<ApplicationInfo> getInstalledApplications(int userId) {
+ final ArrayList<ApplicationInfo> ret = new ArrayList<>();
+
+ addApplicationInfo(getInjectedPackageInfo(CALLING_PACKAGE_1, userId, false), ret);
+ addApplicationInfo(getInjectedPackageInfo(CALLING_PACKAGE_2, userId, false), ret);
+ addApplicationInfo(getInjectedPackageInfo(CALLING_PACKAGE_3, userId, false), ret);
+ addApplicationInfo(getInjectedPackageInfo(CALLING_PACKAGE_4, userId, false), ret);
+ addApplicationInfo(getInjectedPackageInfo(LAUNCHER_1, userId, false), ret);
+ addApplicationInfo(getInjectedPackageInfo(LAUNCHER_2, userId, false), ret);
+ addApplicationInfo(getInjectedPackageInfo(LAUNCHER_3, userId, false), ret);
+ addApplicationInfo(getInjectedPackageInfo(LAUNCHER_4, userId, false), ret);
+
+ return ret;
+ }
+
+ private void addManifestShortcutResource(ComponentName activity, int resId) {
+ final String packageName = activity.getPackageName();
+ HashMap<ComponentName, Integer> map = mActivityMetadataResId.get(packageName);
+ if (map == null) {
+ map = new HashMap<>();
+ mActivityMetadataResId.put(packageName, map);
+ }
+ map.put(activity, resId);
+ }
+
+ private PackageInfo injectGetActivitiesWithMetadata(String packageName, @UserIdInt int userId) {
+ final PackageInfo ret = getInjectedPackageInfo(packageName, userId,
+ /* getSignatures=*/ false);
+
+ final HashMap<ComponentName, Integer> activities = mActivityMetadataResId.get(packageName);
+ if (activities != null) {
+ final ArrayList<ActivityInfo> list = new ArrayList<>();
+
+ for (ComponentName cn : activities.keySet()) {
+ ActivityInfo ai = new ActivityInfo();
+ ai.packageName = cn.getPackageName();
+ ai.name = cn.getClassName();
+ ai.metaData = new Bundle();
+ ai.metaData.putInt(ShortcutParser.METADATA_KEY, activities.get(cn));
+ list.add(ai);
+ }
+ ret.activities = list.toArray(new ActivityInfo[list.size()]);
+ }
+ return ret;
+ }
+
+ private XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) {
+ if (!ShortcutParser.METADATA_KEY.equals(key) || activityInfo.metaData == null) {
+ return null;
+ }
+ final int resId = activityInfo.metaData.getInt(key);
+ return getTestContext().getResources().getXml(resId);
+ }
+
/** Replace the current calling package */
private void setCaller(String packageName, int userId) {
mInjectedClientPackage = packageName;
@@ -883,6 +994,12 @@
makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* weight =*/ 0);
}
+ private ShortcutInfo makeShortcutWithTitle(String id, String title) {
+ return makeShortcut(
+ id, title, /* activity =*/ null, /* icon =*/ null,
+ makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* weight =*/ 0);
+ }
+
/**
* Make a shortcut with an ID and timestamp.
*/
@@ -949,7 +1066,7 @@
Icon icon, Intent intent, int rank) {
final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mClientContext)
.setId(id)
- .setActivityComponent(new ComponentName(mClientContext.getPackageName(), "dummy"))
+ .setActivity(new ComponentName(mClientContext.getPackageName(), "dummy"))
.setTitle(title)
.setRank(rank)
.setIntent(intent);
@@ -957,7 +1074,7 @@
b.setIcon(icon);
}
if (activity != null) {
- b.setActivityComponent(activity);
+ b.setActivity(activity);
}
final ShortcutInfo s = b.build();
@@ -1126,8 +1243,10 @@
return new File(si.getBitmapPath()).getName();
}
- private ShortcutInfo getPackageShortcut(String packageName, String shortcutId) {
- return getPackageShortcut(packageName, shortcutId, getCallingUserId());
+ private List<ShortcutInfo> getCallerShortcuts() {
+ final ShortcutPackage p = mService.getPackageShortcutForTest(
+ getCallingPackage(), getCallingUserId());
+ return p == null ? null : p.getAllShortcutsForTest();
}
private ShortcutInfo getCallerShortcut(String shortcutId) {
@@ -1157,6 +1276,13 @@
return infoList.get(0);
}
+ private Intent genPackageAddIntent(String pakcageName, int userId) {
+ Intent i = new Intent(Intent.ACTION_PACKAGE_ADDED);
+ i.setData(Uri.parse("package:" + pakcageName));
+ i.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ return i;
+ }
+
private Intent genPackageDeleteIntent(String pakcageName, int userId) {
Intent i = new Intent(Intent.ACTION_PACKAGE_REMOVED);
i.setData(Uri.parse("package:" + pakcageName));
@@ -1291,9 +1417,9 @@
// === Test for app side APIs ===
- /** Test for {@link android.content.pm.ShortcutManager#getMaxDynamicShortcutCount()} */
+ /** Test for {@link android.content.pm.ShortcutManager#getMaxShortcutCountForActivity()} */
public void testGetMaxDynamicShortcutCount() {
- assertEquals(MAX_SHORTCUTS, mManager.getMaxDynamicShortcutCount());
+ assertEquals(MAX_SHORTCUTS, mManager.getMaxShortcutCountForActivity());
}
/** Test for {@link android.content.pm.ShortcutManager#getRemainingCallCount()} */
@@ -1301,6 +1427,11 @@
assertEquals(MAX_UPDATES_PER_INTERVAL, mManager.getRemainingCallCount());
}
+ public void getIconMaxDimensions() {
+ assertEquals(MAX_ICON_DIMENSION, mManager.getIconMaxWidth());
+ assertEquals(MAX_ICON_DIMENSION, mManager.getIconMaxHeight());
+ }
+
/** Test for {@link android.content.pm.ShortcutManager#getRateLimitResetTime()} */
public void testGetRateLimitResetTime() {
assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
@@ -2393,7 +2524,7 @@
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
ShortcutInfo si = new ShortcutInfo.Builder(mClientContext)
.setId("id")
- .setActivityComponent(new ComponentName(mClientContext, "dummy"))
+ .setActivity(new ComponentName(mClientContext, "dummy"))
.setTitleResId(10)
.setTextResId(11)
.setDisabledMessageResId(12)
@@ -2404,7 +2535,7 @@
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
ShortcutInfo si = new ShortcutInfo.Builder(mClientContext)
.setId("id")
- .setActivityComponent(new ComponentName(mClientContext, "dummy"))
+ .setActivity(new ComponentName(mClientContext, "dummy"))
.setTitleResId(10)
.setTextResId(11)
.setDisabledMessageResId(12)
@@ -2514,7 +2645,6 @@
}
public void testPinShortcutAndGetPinnedShortcuts() {
- // Create some shortcuts.
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 1000);
final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 2000);
@@ -2551,38 +2681,155 @@
assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
mManager.removeDynamicShortcuts(list("s2"));
assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
+
+ assertShortcutIds(mManager.getDynamicShortcuts(), "s1");
});
runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
assertShortcutIds(mManager.getPinnedShortcuts(), "s3", "s4");
mManager.removeDynamicShortcuts(list("s3"));
assertShortcutIds(mManager.getPinnedShortcuts(), "s3", "s4");
+
+ assertShortcutIds(mManager.getDynamicShortcuts(), "s2", "s4");
});
runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
assertShortcutIds(mManager.getPinnedShortcuts() /* none */);
mManager.removeDynamicShortcuts(list("s2"));
assertShortcutIds(mManager.getPinnedShortcuts() /* none */);
+
+ assertEmpty(mManager.getDynamicShortcuts());
});
// Get pinned shortcuts from launcher
runWithCaller(LAUNCHER_1, USER_0, () -> {
// CALLING_PACKAGE_1 deleted s2, but it's pinned, so it still exists.
- assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
+ assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(assertAllEnabled(
mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
+ /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser())))),
"s2");
+ assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(assertAllEnabled(
+ mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+ /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser())))),
+ "s3", "s4");
+
+ assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(assertAllEnabled(
+ mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_3,
+ /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))))
+ /* none */);
+ });
+ }
+
+ /**
+ * This is similar to the above test, except it used "disable" instead of "remove". It also
+ * does "enable".
+ */
+ public void testDisableAndEnableShortcuts() {
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 1000);
+ final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 2000);
+
+ assertTrue(mManager.setDynamicShortcuts(list(s1_1, s1_2)));
+ });
+
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ final ShortcutInfo s2_2 = makeShortcutWithTimestamp("s2", 1500);
+ final ShortcutInfo s2_3 = makeShortcutWithTimestamp("s3", 3000);
+ final ShortcutInfo s2_4 = makeShortcutWithTimestamp("s4", 500);
+ assertTrue(mManager.setDynamicShortcuts(list(s2_2, s2_3, s2_4)));
+ });
+
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ final ShortcutInfo s3_2 = makeShortcutWithTimestamp("s2", 1000);
+ assertTrue(mManager.setDynamicShortcuts(list(s3_2)));
+ });
+
+ // Pin some.
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+ list("s2", "s3"), getCallingUser());
+
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
+ list("s3", "s4", "s5"), getCallingUser());
+
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_3,
+ list("s3"), getCallingUser()); // Note ID doesn't exist
+ });
+
+ // Disable some.
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
+
+ mManager.disableShortcuts(list("s2"));
+
+ assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
+ assertShortcutIds(mManager.getDynamicShortcuts(), "s1");
+ });
+
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertShortcutIds(mManager.getPinnedShortcuts(), "s3", "s4");
+
+ // disable should work even if a shortcut is not dynamic, so try calling "remove" first
+ // here.
+ mManager.removeDynamicShortcuts(list("s3"));
+ mManager.disableShortcuts(list("s3"));
+
+ assertShortcutIds(mManager.getPinnedShortcuts(), "s3", "s4");
+ assertShortcutIds(mManager.getDynamicShortcuts(), "s2", "s4");
+ });
+
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertShortcutIds(mManager.getPinnedShortcuts() /* none */);
+
+ mManager.disableShortcuts(list("s2"));
+
+ assertShortcutIds(mManager.getPinnedShortcuts() /* none */);
+
+ assertEmpty(mManager.getDynamicShortcuts());
+ assertEmpty(getCallerShortcuts());
+ });
+
+ // Get pinned shortcuts from launcher
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ // CALLING_PACKAGE_1 deleted s2, but it's pinned, so it still exists.
+ assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(assertAllDisabled(
+ mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+ /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser())))),
+ "s2");
+ assertFalse(mLauncherApps.startShortcut(
+ CALLING_PACKAGE_1, "s2", null, null, HANDLE_USER_0));
+
assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
/* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
"s3", "s4");
+ assertFalse(mLauncherApps.startShortcut(
+ CALLING_PACKAGE_2, "s3", null, null, HANDLE_USER_0));
+ assertTrue(mLauncherApps.startShortcut(
+ CALLING_PACKAGE_2, "s4", null, null, HANDLE_USER_0));
- assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
+ assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(assertAllEnabled(
mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_3,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser())))
+ /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))))
/* none */);
});
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ mManager.enableShortcuts(list("s2"));
+
+ assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
+ assertShortcutIds(mManager.getDynamicShortcuts(), "s1");
+ });
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ // CALLING_PACKAGE_1 deleted s2, but it's pinned, so it still exists.
+ assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(assertAllEnabled(
+ mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+ /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser())))),
+ "s2");
+ assertTrue(mLauncherApps.startShortcut(
+ CALLING_PACKAGE_1, "s2", null, null, HANDLE_USER_0));
+ });
}
public void testPinShortcutAndGetPinnedShortcuts_multi() {
@@ -6007,14 +6254,14 @@
() -> new ShortcutInfo.Builder(getTestContext()).setIntent(new Intent()));
assertExpectException(
NullPointerException.class,
- "activityComponent must be provided",
+ "activity must be provided",
() -> new ShortcutInfo.Builder(getTestContext()).setId("id").build()
.enforceMandatoryFields());
assertExpectException(
IllegalArgumentException.class,
"title must be provided",
() -> new ShortcutInfo.Builder(getTestContext()).setId("id")
- .setActivityComponent(
+ .setActivity(
new ComponentName(getTestContext().getPackageName(), "s"))
.build()
.enforceMandatoryFields());
@@ -6022,7 +6269,7 @@
NullPointerException.class,
"Intent must be provided",
() -> new ShortcutInfo.Builder(getTestContext()).setId("id")
- .setActivityComponent(
+ .setActivity(
new ComponentName(getTestContext().getPackageName(), "s"))
.setTitle("x").build()
.enforceMandatoryFields());
@@ -6035,7 +6282,7 @@
.setTitle("title")
.setIntent(makeIntent("action", ShortcutActivity.class))
.build());
- assertEquals(mClientContext.getPackageName(), si.getPackageName());
+ assertEquals(mClientContext.getPackageName(), si.getPackage());
assertEquals(USER_10, si.getUserId());
assertEquals(HANDLE_USER_10, si.getUserHandle());
assertEquals("id", si.getId());
@@ -6047,7 +6294,7 @@
si = new ShortcutInfo.Builder(getTestContext())
.setId("id")
- .setActivityComponent(new ComponentName("a", "b"))
+ .setActivity(new ComponentName("a", "b"))
.setIcon(Icon.createWithResource(mClientContext, 123))
.setTitle("title")
.setText("text")
@@ -6063,9 +6310,9 @@
si = parceled(si);
- assertEquals(getTestContext().getPackageName(), si.getPackageName());
+ assertEquals(getTestContext().getPackageName(), si.getPackage());
assertEquals("id", si.getId());
- assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+ assertEquals(new ComponentName("a", "b"), si.getActivity());
assertEquals(123, si.getIcon().getResId());
assertEquals("title", si.getTitle());
assertEquals("text", si.getText());
@@ -6090,7 +6337,7 @@
si = new ShortcutInfo.Builder(getTestContext())
.setId("id")
- .setActivityComponent(new ComponentName("a", "b"))
+ .setActivity(new ComponentName("a", "b"))
.setIcon(Icon.createWithResource(mClientContext, 123))
.setTitleResId(10)
.setTextResId(11)
@@ -6106,9 +6353,9 @@
si = parceled(si);
- assertEquals(getTestContext().getPackageName(), si.getPackageName());
+ assertEquals(getTestContext().getPackageName(), si.getPackage());
assertEquals("id", si.getId());
- assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+ assertEquals(new ComponentName("a", "b"), si.getActivity());
assertEquals(123, si.getIcon().getResId());
assertEquals(10, si.getTitleResId());
assertEquals(11, si.getTextResId());
@@ -6131,7 +6378,7 @@
pb.putInt("k", 1);
ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
.setId("id")
- .setActivityComponent(new ComponentName("a", "b"))
+ .setActivity(new ComponentName("a", "b"))
.setIcon(Icon.createWithResource(mClientContext, 123))
.setTitle("title")
.setText("text")
@@ -6149,9 +6396,9 @@
assertEquals(USER_11, si.getUserId());
assertEquals(HANDLE_USER_11, si.getUserHandle());
- assertEquals(mClientContext.getPackageName(), si.getPackageName());
+ assertEquals(mClientContext.getPackageName(), si.getPackage());
assertEquals("id", si.getId());
- assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+ assertEquals(new ComponentName("a", "b"), si.getActivity());
assertEquals(123, si.getIcon().getResId());
assertEquals("title", si.getTitle());
assertEquals("text", si.getText());
@@ -6168,9 +6415,9 @@
si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
- assertEquals(mClientContext.getPackageName(), si.getPackageName());
+ assertEquals(mClientContext.getPackageName(), si.getPackage());
assertEquals("id", si.getId());
- assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+ assertEquals(new ComponentName("a", "b"), si.getActivity());
assertEquals(null, si.getIcon());
assertEquals("title", si.getTitle());
assertEquals("text", si.getText());
@@ -6188,9 +6435,9 @@
si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
- assertEquals(mClientContext.getPackageName(), si.getPackageName());
+ assertEquals(mClientContext.getPackageName(), si.getPackage());
assertEquals("id", si.getId());
- assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+ assertEquals(new ComponentName("a", "b"), si.getActivity());
assertEquals(null, si.getIcon());
assertEquals("title", si.getTitle());
assertEquals("text", si.getText());
@@ -6207,9 +6454,9 @@
si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
- assertEquals(mClientContext.getPackageName(), si.getPackageName());
+ assertEquals(mClientContext.getPackageName(), si.getPackage());
assertEquals("id", si.getId());
- assertEquals(null, si.getActivityComponent());
+ assertEquals(null, si.getActivity());
assertEquals(null, si.getIcon());
assertEquals(null, si.getTitle());
assertEquals(null, si.getText());
@@ -6232,7 +6479,7 @@
pb.putInt("k", 1);
ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
.setId("id")
- .setActivityComponent(new ComponentName("a", "b"))
+ .setActivity(new ComponentName("a", "b"))
.setIcon(Icon.createWithResource(mClientContext, 123))
.setTitleResId(10)
.setTextResId(11)
@@ -6250,9 +6497,9 @@
assertEquals(USER_11, si.getUserId());
assertEquals(HANDLE_USER_11, si.getUserHandle());
- assertEquals(mClientContext.getPackageName(), si.getPackageName());
+ assertEquals(mClientContext.getPackageName(), si.getPackage());
assertEquals("id", si.getId());
- assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+ assertEquals(new ComponentName("a", "b"), si.getActivity());
assertEquals(123, si.getIcon().getResId());
assertEquals(10, si.getTitleResId());
assertEquals(11, si.getTextResId());
@@ -6269,9 +6516,9 @@
si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
- assertEquals(mClientContext.getPackageName(), si.getPackageName());
+ assertEquals(mClientContext.getPackageName(), si.getPackage());
assertEquals("id", si.getId());
- assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+ assertEquals(new ComponentName("a", "b"), si.getActivity());
assertEquals(null, si.getIcon());
assertEquals(10, si.getTitleResId());
assertEquals(11, si.getTextResId());
@@ -6289,9 +6536,9 @@
si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
- assertEquals(mClientContext.getPackageName(), si.getPackageName());
+ assertEquals(mClientContext.getPackageName(), si.getPackage());
assertEquals("id", si.getId());
- assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+ assertEquals(new ComponentName("a", "b"), si.getActivity());
assertEquals(null, si.getIcon());
assertEquals(10, si.getTitleResId());
assertEquals(11, si.getTextResId());
@@ -6308,9 +6555,9 @@
si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
- assertEquals(mClientContext.getPackageName(), si.getPackageName());
+ assertEquals(mClientContext.getPackageName(), si.getPackage());
assertEquals("id", si.getId());
- assertEquals(null, si.getActivityComponent());
+ assertEquals(null, si.getActivity());
assertEquals(null, si.getIcon());
assertEquals(0, si.getTitleResId());
assertEquals(0, si.getTextResId());
@@ -6336,7 +6583,7 @@
.build();
ShortcutInfo si = sorig.clone(/* clone flags*/ 0);
- assertEquals(getTestContext().getPackageName(), si.getPackageName());
+ assertEquals(getTestContext().getPackageName(), si.getPackage());
assertEquals("id", si.getId());
assertEquals("title", si.getTitle());
assertEquals("action", si.getIntent().getAction());
@@ -6344,7 +6591,7 @@
si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
- assertEquals(getTestContext().getPackageName(), si.getPackageName());
+ assertEquals(getTestContext().getPackageName(), si.getPackage());
assertEquals("id", si.getId());
assertEquals("title", si.getTitle());
assertEquals("action", si.getIntent().getAction());
@@ -6352,7 +6599,7 @@
si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
- assertEquals(getTestContext().getPackageName(), si.getPackageName());
+ assertEquals(getTestContext().getPackageName(), si.getPackage());
assertEquals("id", si.getId());
assertEquals("title", si.getTitle());
assertEquals(null, si.getIntent());
@@ -6360,7 +6607,7 @@
si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
- assertEquals(getTestContext().getPackageName(), si.getPackageName());
+ assertEquals(getTestContext().getPackageName(), si.getPackage());
assertEquals("id", si.getId());
assertEquals(null, si.getTitle());
assertEquals(null, si.getIntent());
@@ -6372,7 +6619,7 @@
pb.putInt("k", 1);
ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext())
.setId("id")
- .setActivityComponent(new ComponentName("a", "b"))
+ .setActivity(new ComponentName("a", "b"))
.setIcon(Icon.createWithResource(mClientContext, 123))
.setTitle("title")
.setText("text")
@@ -6390,9 +6637,9 @@
si = sorig.clone(/* flags=*/ 0);
si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
- .setActivityComponent(new ComponentName("x", "y")).build());
+ .setActivity(new ComponentName("x", "y")).build());
assertEquals("text", si.getText());
- assertEquals(new ComponentName("x", "y"), si.getActivityComponent());
+ assertEquals(new ComponentName("x", "y"), si.getActivity());
si = sorig.clone(/* flags=*/ 0);
si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
@@ -6500,7 +6747,7 @@
pb.putInt("k", 1);
ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext())
.setId("id")
- .setActivityComponent(new ComponentName("a", "b"))
+ .setActivity(new ComponentName("a", "b"))
.setIcon(Icon.createWithResource(mClientContext, 123))
.setTitleResId(10)
.setTextResId(11)
@@ -6518,9 +6765,9 @@
si = sorig.clone(/* flags=*/ 0);
si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
- .setActivityComponent(new ComponentName("x", "y")).build());
+ .setActivity(new ComponentName("x", "y")).build());
assertEquals(11, si.getTextResId());
- assertEquals(new ComponentName("x", "y"), si.getActivityComponent());
+ assertEquals(new ComponentName("x", "y"), si.getActivity());
si = sorig.clone(/* flags=*/ 0);
si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
@@ -6633,7 +6880,7 @@
pb.putInt("k", 1);
ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
.setId("id")
- .setActivityComponent(new ComponentName(mClientContext, ShortcutActivity2.class))
+ .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
.setIcon(bmp32x32)
.setTitle("title")
.setText("text")
@@ -6659,9 +6906,9 @@
assertEquals(USER_10, si.getUserId());
assertEquals(HANDLE_USER_10, si.getUserHandle());
- assertEquals(CALLING_PACKAGE_1, si.getPackageName());
+ assertEquals(CALLING_PACKAGE_1, si.getPackage());
assertEquals("id", si.getId());
- assertEquals(ShortcutActivity2.class.getName(), si.getActivityComponent().getClassName());
+ assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName());
assertEquals(null, si.getIcon());
assertEquals("title", si.getTitle());
assertEquals("text", si.getText());
@@ -6688,7 +6935,7 @@
pb.putInt("k", 1);
ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
.setId("id")
- .setActivityComponent(new ComponentName(mClientContext, ShortcutActivity2.class))
+ .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
.setIcon(bmp32x32)
.setTitleResId(10)
.setTextResId(11)
@@ -6714,9 +6961,9 @@
assertEquals(USER_10, si.getUserId());
assertEquals(HANDLE_USER_10, si.getUserHandle());
- assertEquals(CALLING_PACKAGE_1, si.getPackageName());
+ assertEquals(CALLING_PACKAGE_1, si.getPackage());
assertEquals("id", si.getId());
- assertEquals(ShortcutActivity2.class.getName(), si.getActivityComponent().getClassName());
+ assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName());
assertEquals(null, si.getIcon());
assertEquals(10, si.getTitleResId());
assertEquals(11, si.getTextResId());
@@ -6743,7 +6990,7 @@
pb.putInt("k", 1);
ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
.setId("id")
- .setActivityComponent(new ComponentName(mClientContext, ShortcutActivity2.class))
+ .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
.setIcon(bmp32x32)
.setTitle("title")
.setText("text")
@@ -6768,9 +7015,9 @@
ShortcutInfo si;
si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_0);
- assertEquals(CALLING_PACKAGE_1, si.getPackageName());
+ assertEquals(CALLING_PACKAGE_1, si.getPackage());
assertEquals("id", si.getId());
- assertEquals(ShortcutActivity2.class.getName(), si.getActivityComponent().getClassName());
+ assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName());
assertEquals(null, si.getIcon());
assertEquals("title", si.getTitle());
assertEquals("text", si.getText());
@@ -6796,7 +7043,7 @@
pb.putInt("k", 1);
ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
.setId("id")
- .setActivityComponent(new ComponentName(mClientContext, ShortcutActivity2.class))
+ .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
.setIcon(bmp32x32)
.setTitleResId(10)
.setTextResId(11)
@@ -6821,9 +7068,9 @@
ShortcutInfo si;
si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_0);
- assertEquals(CALLING_PACKAGE_1, si.getPackageName());
+ assertEquals(CALLING_PACKAGE_1, si.getPackage());
assertEquals("id", si.getId());
- assertEquals(ShortcutActivity2.class.getName(), si.getActivityComponent().getClassName());
+ assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName());
assertEquals(null, si.getIcon());
assertEquals(10, si.getTitleResId());
assertEquals(11, si.getTextResId());
@@ -6849,4 +7096,810 @@
// Dump after having some icons.
dumpsysOnLogcat("test1", /* force= */ true);
}
+
+ public void testManifestShortcut_publishOnUnlockUser() {
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_1);
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
+ R.xml.shortcut_2);
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_3, ShortcutActivity.class.getName()),
+ R.xml.shortcut_5);
+
+ // Unlock user-0.
+ mService.handleUnlockUser(USER_0);
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1");
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1", "ms2");
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1", "ms2", "ms3", "ms4", "ms5");
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+
+ // Try on another user, with some packages uninstalled.
+ uninstallPackage(USER_10, CALLING_PACKAGE_1);
+ uninstallPackage(USER_10, CALLING_PACKAGE_3);
+
+ mService.handleUnlockUser(USER_10);
+
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertEmpty(mManager.getManifestShortcuts());
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+
+ runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1", "ms2");
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+
+ runWithCaller(CALLING_PACKAGE_3, USER_10, () -> {
+ assertEmpty(mManager.getManifestShortcuts());
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+
+ // Now change the resources for package 1, and unlock again.
+ // But we still see *old* shortcuts, because the package version and install time
+ // hasn't changed.
+ shutdownServices();
+
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_5);
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_3, ShortcutActivity.class.getName()),
+ R.xml.shortcut_1);
+
+ initService();
+ mService.handleUnlockUser(USER_0);
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1");
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1", "ms2");
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1", "ms2", "ms3", "ms4", "ms5");
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+
+ // Do it again, but this time we change the app version, so we do detect the changes.
+ shutdownServices();
+
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ updatePackageLastUpdateTime(CALLING_PACKAGE_3, 1);
+
+ initService();
+ mService.handleUnlockUser(USER_0);
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1", "ms2", "ms3", "ms4", "ms5");
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1", "ms2");
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1");
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+
+ // Next, try removing all shortcuts, with some of them pinned.
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms3"), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("ms2"), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("ms1"), HANDLE_USER_0);
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1", "ms2", "ms3", "ms4", "ms5");
+ assertShortcutIds(assertAllImmutable(assertAllPinned(assertAllManifest(
+ assertAllEnabled(mManager.getPinnedShortcuts())))),
+ "ms3");
+ });
+
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1", "ms2");
+ assertShortcutIds(assertAllImmutable(assertAllPinned(assertAllManifest(
+ assertAllEnabled(mManager.getPinnedShortcuts())))),
+ "ms2");
+ });
+
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1");
+ assertShortcutIds(assertAllImmutable(assertAllPinned(assertAllManifest(
+ assertAllEnabled(mManager.getPinnedShortcuts())))),
+ "ms1");
+ });
+
+ shutdownServices();
+
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_0);
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
+ R.xml.shortcut_1);
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_3, ShortcutActivity.class.getName()),
+ R.xml.shortcut_0);
+
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ updatePackageVersion(CALLING_PACKAGE_2, 1);
+ updatePackageVersion(CALLING_PACKAGE_3, 1);
+
+ initService();
+ mService.handleUnlockUser(USER_0);
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertEmpty(mManager.getManifestShortcuts());
+ assertShortcutIds(assertAllImmutable(assertAllPinned(assertAllNotManifest(
+ assertAllDisabled(mManager.getPinnedShortcuts())))),
+ "ms3");
+ });
+
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1");
+ assertShortcutIds(assertAllImmutable(assertAllPinned(assertAllNotManifest(
+ assertAllDisabled(mManager.getPinnedShortcuts())))),
+ "ms2");
+ });
+
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertEmpty(mManager.getManifestShortcuts());
+ assertShortcutIds(assertAllImmutable(assertAllPinned(assertAllNotManifest(
+ assertAllDisabled(mManager.getPinnedShortcuts())))),
+ "ms1");
+ });
+
+ // Make sure we don't have ShortcutPackage for packages that don't have shortcuts.
+ assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_4, USER_0));
+ assertNull(mService.getPackageShortcutForTest(LAUNCHER_1, USER_0));
+ }
+
+
+ public void testManifestShortcut_publishOnBroadcast() {
+ // First, no packages are installed.
+ uninstallPackage(USER_0, CALLING_PACKAGE_1);
+ uninstallPackage(USER_0, CALLING_PACKAGE_2);
+ uninstallPackage(USER_0, CALLING_PACKAGE_3);
+ uninstallPackage(USER_0, CALLING_PACKAGE_4);
+ uninstallPackage(USER_10, CALLING_PACKAGE_1);
+ uninstallPackage(USER_10, CALLING_PACKAGE_2);
+ uninstallPackage(USER_10, CALLING_PACKAGE_3);
+ uninstallPackage(USER_10, CALLING_PACKAGE_4);
+
+ mService.handleUnlockUser(USER_0);
+ mService.handleUnlockUser(USER_10);
+
+ // Originally no manifest shortcuts.
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertEmpty(mManager.getManifestShortcuts());
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertEmpty(mManager.getManifestShortcuts());
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertEmpty(mManager.getManifestShortcuts());
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+
+ // Package 1 updated, with manifest shortcuts.
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_1);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1");
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertEmpty(mManager.getManifestShortcuts());
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+
+ // Package 2 updated, with manifest shortcuts.
+
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
+ R.xml.shortcut_5);
+ updatePackageVersion(CALLING_PACKAGE_2, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_2, USER_0));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1");
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1", "ms2", "ms3", "ms4", "ms5");
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+
+ // Package 2 updated, with less manifest shortcuts.
+ // This time we use updatePackageLastUpdateTime() instead of updatePackageVersion().
+
+ dumpsysOnLogcat("Before pinning");
+
+ // Also pin some.
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("ms2", "ms3"), HANDLE_USER_0);
+ });
+
+ dumpsysOnLogcat("After pinning");
+
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
+ R.xml.shortcut_2);
+ updatePackageLastUpdateTime(CALLING_PACKAGE_2, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_2, USER_0));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1");
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1", "ms2");
+ assertShortcutIds(assertAllImmutable(assertAllPinned(
+ mManager.getPinnedShortcuts())),
+ "ms2", "ms3");
+ // ms3 is no longer in manifest, so should be disabled.
+ // but ms1 and ms2 should be enabled.
+ assertAllEnabled(list(getCallerShortcut("ms1")));
+ assertAllEnabled(list(getCallerShortcut("ms2")));
+ assertAllDisabled(list(getCallerShortcut("ms3")));
+ });
+
+ // Package 2 on user 10 has no shortcuts yet.
+ runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
+ assertEmpty(mManager.getManifestShortcuts());
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+ // Send PACKAGE_ADD broadcast to have Package 2 on user-10 publish manifest shortcuts.
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_2, USER_10));
+
+ runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1", "ms2");
+ assertEmpty(mManager.getPinnedShortcuts());
+ });
+
+ // But it shouldn't affect user-0.
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1", "ms2");
+ assertShortcutIds(assertAllImmutable(assertAllPinned(
+ mManager.getPinnedShortcuts())),
+ "ms2", "ms3");
+ assertAllEnabled(list(getCallerShortcut("ms1")));
+ assertAllEnabled(list(getCallerShortcut("ms2")));
+ assertAllDisabled(list(getCallerShortcut("ms3")));
+ });
+
+ // Package 2 now has no manifest shortcuts.
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
+ R.xml.shortcut_0);
+ updatePackageLastUpdateTime(CALLING_PACKAGE_2, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_2, USER_0));
+
+ // No manifest shortcuts, and pinned ones are disabled.
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertEmpty(mManager.getManifestShortcuts());
+ assertShortcutIds(assertAllImmutable(assertAllPinned(assertAllDisabled(
+ mManager.getPinnedShortcuts()))),
+ "ms2", "ms3");
+ });
+ }
+
+ public void testManifestShortcuts_missingMandatoryFields() {
+ // Start with no apps installed.
+ uninstallPackage(USER_0, CALLING_PACKAGE_1);
+ uninstallPackage(USER_0, CALLING_PACKAGE_2);
+ uninstallPackage(USER_0, CALLING_PACKAGE_3);
+ uninstallPackage(USER_0, CALLING_PACKAGE_4);
+
+ mService.handleUnlockUser(USER_0);
+
+ // Make sure no manifest shortcuts.
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertEmpty(mManager.getManifestShortcuts());
+ });
+
+ // Package 1 updated, which has one valid manifest shortcut and one invalid.
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_error_1);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+ // Only the valid one is published.
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "x1");
+ });
+
+ // Package 1 updated, which has one valid manifest shortcut and one invalid.
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_error_2);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+ // Only the valid one is published.
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "x2");
+ });
+
+ // Package 1 updated, which has one valid manifest shortcut and one invalid.
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_error_3);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+ // Only the valid one is published.
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "x3");
+ });
+ }
+
+ public void testManifestShortcuts_checkAllFields() {
+ mService.handleUnlockUser(USER_0);
+
+ // Package 1 updated, which has one valid manifest shortcut and one invalid.
+ 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));
+
+ // Only the valid one is published.
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1", "ms2", "ms3", "ms4", "ms5");
+
+ // check first shortcut.
+ ShortcutInfo si = getCallerShortcut("ms1");
+
+ 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.string.shortcut_text1, si.getTextResId());
+ assertEquals(R.string.shortcut_disabled_message1, si.getDisabledMessageResId());
+ 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());
+
+ // check another
+ si = getCallerShortcut("ms2");
+
+ assertEquals("ms2", si.getId());
+ assertEquals(R.drawable.icon2, si.getIconResourceId());
+ assertEquals(R.string.shortcut_title2, si.getTitleResId());
+ assertEquals(R.string.shortcut_text2, si.getTextResId());
+ assertEquals(R.string.shortcut_disabled_message2, si.getDisabledMessageResId());
+ assertEquals(set("android.shortcut.conversation"), si.getCategories());
+ assertEquals("action2", si.getIntent().getAction());
+ assertEquals(null, si.getIntent().getData());
+
+ // check another
+ si = getCallerShortcut("ms3");
+
+ assertEquals("ms3", si.getId());
+ assertEquals(0, si.getIconResourceId());
+ assertEquals(R.string.shortcut_title1, si.getTitleResId());
+ assertEquals(0, si.getTextResId());
+ assertEquals(0, si.getDisabledMessageResId());
+ assertEquals(null, si.getCategories());
+ assertEquals("android.intent.action.VIEW", si.getIntent().getAction());
+ assertEquals(null, si.getIntent().getData());
+ });
+ }
+
+ public void testManifestShortcuts_updateAndDisabled_notPinned() {
+ mService.handleUnlockUser(USER_0);
+
+ // First, just publish a manifest shortcut.
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_1);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+ // Only the valid one is published.
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1");
+ assertEmpty(mManager.getPinnedShortcuts());
+
+ // Make sure there's no other dangling shortcuts.
+ assertShortcutIds(getCallerShortcuts(), "ms1");
+ });
+
+ // Now version up, the manifest shortcut is disabled now.
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_1_disable);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+ // Because shortcut 1 wasn't pinned, it'll just go away.
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertEmpty(mManager.getManifestShortcuts());
+ assertEmpty(mManager.getPinnedShortcuts());
+
+ // Make sure there's no other dangling shortcuts.
+ assertEmpty(getCallerShortcuts());
+ });
+ }
+
+ public void testManifestShortcuts_updateAndDisabled_pinned() {
+ mService.handleUnlockUser(USER_0);
+
+ // First, just publish a manifest shortcut.
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_1);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+ // Only the valid one is published.
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1");
+ assertEmpty(mManager.getPinnedShortcuts());
+
+ // Make sure there's no other dangling shortcuts.
+ assertShortcutIds(getCallerShortcuts(), "ms1");
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_0);
+ });
+
+ // Now upgrade, the manifest shortcut is disabled now.
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_1_disable);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+ // Because shortcut 1 was pinned, it'll still exist as pinned, but disabled.
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertEmpty(mManager.getManifestShortcuts());
+ assertShortcutIds(assertAllNotManifest(assertAllImmutable(assertAllDisabled(
+ mManager.getPinnedShortcuts()))),
+ "ms1");
+
+ // Make sure the fields are updated.
+ ShortcutInfo si = getCallerShortcut("ms1");
+
+ assertEquals("ms1", si.getId());
+ assertEquals(R.drawable.icon2, si.getIconResourceId());
+ assertEquals(R.string.shortcut_title2, si.getTitleResId());
+ assertEquals(R.string.shortcut_text2, si.getTextResId());
+ assertEquals(R.string.shortcut_disabled_message2, si.getDisabledMessageResId());
+ assertEquals(Intent.ACTION_VIEW, si.getIntent().getAction());
+
+ // Make sure there's no other dangling shortcuts.
+ assertShortcutIds(getCallerShortcuts(), "ms1");
+ });
+ }
+
+ public void testManifestShortcuts_duplicateInSingleActivity() {
+ mService.handleUnlockUser(USER_0);
+
+ // The XML has two shortcuts with the same ID.
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_2_duplicate);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1");
+
+ // Make sure the first one has survived. (the second one has a different title.)
+ ShortcutInfo si = getCallerShortcut("ms1");
+ assertEquals(R.string.shortcut_title1, si.getTitleResId());
+
+ // Make sure there's no other dangling shortcuts.
+ assertShortcutIds(getCallerShortcuts(), "ms1");
+ });
+ }
+
+ public void testManifestShortcuts_duplicateInTwoActivities() {
+ mService.handleUnlockUser(USER_0);
+
+ // ShortcutActivity has shortcut ms1
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_1);
+
+ // ShortcutActivity2 has two shortcuts, ms1 and ms2.
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
+ R.xml.shortcut_2);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
+ mManager.getManifestShortcuts()))),
+ "ms1", "ms2");
+
+ // ms1 should belong to ShortcutActivity.
+ ShortcutInfo si = getCallerShortcut("ms1");
+ assertEquals(R.string.shortcut_title1, si.getTitleResId());
+ assertEquals(new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ si.getActivity());
+
+ // ms2 should belong to ShortcutActivity*2*.
+ si = getCallerShortcut("ms2");
+ assertEquals(R.string.shortcut_title2, si.getTitleResId());
+ assertEquals(new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
+ si.getActivity());
+
+ // Make sure there's no other dangling shortcuts.
+ assertShortcutIds(getCallerShortcuts(), "ms1", "ms2");
+ });
+ }
+
+ /**
+ * Manifest shortcuts cannot override shortcuts that were published via the APIs.
+ */
+ public void testManifestShortcuts_cannotOverrideNonManifest() {
+ mService.handleUnlockUser(USER_0);
+
+ // Create a non-pinned dynamic shortcut and a non-dynamic pinned shortcut.
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ mManager.setDynamicShortcuts(list(
+ makeShortcut("ms1", "title1",
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ /* icon */ null, new Intent("action1"), /* rank */ 0),
+ makeShortcut("ms2", "title2",
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ /* icon */ null, new Intent("action1"), /* rank */ 0)));
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms2"), HANDLE_USER_0);
+ });
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ mManager.removeDynamicShortcuts(list("ms2"));
+
+ assertShortcutIds(mManager.getDynamicShortcuts(), "ms1");
+ assertShortcutIds(mManager.getPinnedShortcuts(), "ms2");
+ assertEmpty(mManager.getManifestShortcuts());
+ });
+
+ // Then update the app with 5 manifest shortcuts.
+ // Make sure "ms1" and "ms2" won't be replaced.
+ 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, () -> {
+ assertShortcutIds(assertAllNotManifest(mManager.getDynamicShortcuts()), "ms1");
+ assertShortcutIds(assertAllNotManifest(mManager.getPinnedShortcuts()), "ms2");
+ assertShortcutIds(assertAllManifest(mManager.getManifestShortcuts()),
+ "ms3", "ms4", "ms5");
+
+ // ms1 and ms2 shouold keep the original title.
+ ShortcutInfo si = getCallerShortcut("ms1");
+ assertEquals("title1", si.getTitle());
+
+ si = getCallerShortcut("ms2");
+ assertEquals("title2", si.getTitle());
+ });
+ }
+
+ private void checkManifestShortcuts_immutable_verify() {
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertShortcutIds(assertAllNotManifest(assertAllEnabled(
+ mManager.getDynamicShortcuts())),
+ "s1");
+ assertShortcutIds(assertAllManifest(assertAllEnabled(
+ mManager.getManifestShortcuts())),
+ "ms1");
+ assertShortcutIds(assertAllNotManifest(assertAllDisabled(
+ mManager.getPinnedShortcuts())),
+ "ms2");
+
+ assertEquals("t1", getCallerShortcut("s1").getTitle());
+
+ // Make sure there are no other shortcuts.
+ assertShortcutIds(getCallerShortcuts(), "s1", "ms1", "ms2");
+ });
+ }
+
+ /**
+ * Make sure the APIs won't work on manifest shortcuts.
+ */
+ public void testManifestShortcuts_immutable() {
+ mService.handleUnlockUser(USER_0);
+
+ // Create a non-pinned manifest shortcut, a pinned shortcut that was originally
+ // a manifest shortcut, as well as a dynamic shortcut.
+
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_2);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms2"), HANDLE_USER_0);
+ });
+
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_1);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ mManager.addDynamicShortcuts(list(makeShortcutWithTitle("s1", "t1")));
+ });
+
+ checkManifestShortcuts_immutable_verify();
+
+ // Note that even though the first argument is not immutable and only the second one
+ // is immutable, the first argument should not be executed either.
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertCannotUpdateImmutable(() -> {
+ mManager.setDynamicShortcuts(list(makeShortcut("xx"), makeShortcut("ms1")));
+ });
+ assertCannotUpdateImmutable(() -> {
+ mManager.setDynamicShortcuts(list(makeShortcut("xx"), makeShortcut("ms2")));
+ });
+ });
+ checkManifestShortcuts_immutable_verify();
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertCannotUpdateImmutable(() -> {
+ mManager.addDynamicShortcuts(list(makeShortcut("xx"), makeShortcut("ms1")));
+ });
+ assertCannotUpdateImmutable(() -> {
+ mManager.addDynamicShortcuts(list(makeShortcut("xx"), makeShortcut("ms2")));
+ });
+ });
+ checkManifestShortcuts_immutable_verify();
+
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertCannotUpdateImmutable(() -> {
+ mManager.updateShortcuts(list(makeShortcut("s1"), makeShortcut("ms1")));
+ });
+ assertCannotUpdateImmutable(() -> {
+ mManager.updateShortcuts(list(makeShortcut("s1"), makeShortcut("ms2")));
+ });
+ });
+ checkManifestShortcuts_immutable_verify();
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertCannotUpdateImmutable(() -> {
+ mManager.removeDynamicShortcuts(list("s1", "ms1"));
+ });
+ assertCannotUpdateImmutable(() -> {
+ mManager.removeDynamicShortcuts(list("s2", "ms2"));
+ });
+ });
+ checkManifestShortcuts_immutable_verify();
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertCannotUpdateImmutable(() -> {
+ mManager.disableShortcuts(list("s1", "ms1"));
+ });
+ });
+ checkManifestShortcuts_immutable_verify();
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertCannotUpdateImmutable(() -> {
+ mManager.enableShortcuts(list("s1", "ms2"));
+ });
+ });
+ checkManifestShortcuts_immutable_verify();
+ }
}
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 c23f9e6..7fa8a800 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
@@ -246,11 +246,22 @@
reset(o);
}
}
+
+ public static <T> List<T> assertEmpty(List<T> list) {
+ assertEquals(0, list.size());
+ return list;
+ }
+
public static void assertExpectException(Class<? extends Throwable> expectedExceptionType,
String expectedExceptionMessageRegex, Runnable r) {
assertExpectException("", expectedExceptionType, expectedExceptionMessageRegex, r);
}
+ public static void assertCannotUpdateImmutable(Runnable r) {
+ assertExpectException(
+ IllegalArgumentException.class, "may not be manipulated via APIs", r);
+ }
+
public static void assertDynamicShortcutCountExceeded(Runnable r) {
assertExpectException(IllegalArgumentException.class,
"Max number of dynamic shortcuts exceeded", r);
@@ -383,6 +394,46 @@
return actualShortcuts;
}
+ public static List<ShortcutInfo> assertAllManifest(
+ List<ShortcutInfo> actualShortcuts) {
+ for (ShortcutInfo s : actualShortcuts) {
+ assertTrue("ID " + s.getId(), s.isManifestShortcut());
+ }
+ return actualShortcuts;
+ }
+
+ public static List<ShortcutInfo> assertAllNotManifest(
+ List<ShortcutInfo> actualShortcuts) {
+ for (ShortcutInfo s : actualShortcuts) {
+ assertFalse("ID " + s.getId(), s.isManifestShortcut());
+ }
+ return actualShortcuts;
+ }
+
+ public static List<ShortcutInfo> assertAllDisabled(
+ List<ShortcutInfo> actualShortcuts) {
+ for (ShortcutInfo s : actualShortcuts) {
+ assertTrue("ID " + s.getId(), !s.isEnabled());
+ }
+ return actualShortcuts;
+ }
+
+ public static List<ShortcutInfo> assertAllEnabled(
+ List<ShortcutInfo> actualShortcuts) {
+ for (ShortcutInfo s : actualShortcuts) {
+ assertTrue("ID " + s.getId(), s.isEnabled());
+ }
+ return actualShortcuts;
+ }
+
+ public static List<ShortcutInfo> assertAllImmutable(
+ List<ShortcutInfo> actualShortcuts) {
+ for (ShortcutInfo s : actualShortcuts) {
+ assertTrue("ID " + s.getId(), s.isImmutable());
+ }
+ return actualShortcuts;
+ }
+
public static List<ShortcutInfo> assertAllStringsResolved(
List<ShortcutInfo> actualShortcuts) {
for (ShortcutInfo s : actualShortcuts) {
@@ -398,6 +449,7 @@
public static void assertPinnedOnly(ShortcutInfo si) {
assertFalse(si.isDynamic());
+ assertFalse(si.isManifestShortcut());
assertTrue(si.isPinned());
}