diff options
| -rw-r--r-- | api/current.txt | 12 | ||||
| -rw-r--r-- | api/system-current.txt | 12 | ||||
| -rw-r--r-- | api/test-current.txt | 12 | ||||
| -rw-r--r-- | core/java/android/content/pm/IShortcutService.aidl | 7 | ||||
| -rw-r--r-- | core/java/android/content/pm/LauncherApps.java | 38 | ||||
| -rw-r--r-- | core/java/android/content/pm/ShortcutInfo.java | 63 | ||||
| -rw-r--r-- | core/java/android/content/pm/ShortcutManager.java | 25 | ||||
| -rwxr-xr-x | core/java/android/provider/Settings.java | 4 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/ShortcutPackage.java | 34 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/ShortcutService.java | 51 | ||||
| -rw-r--r-- | services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java | 227 |
11 files changed, 328 insertions, 157 deletions
diff --git a/api/current.txt b/api/current.txt index 732c50080623..9dd271033a05 100644 --- a/api/current.txt +++ b/api/current.txt @@ -9500,8 +9500,6 @@ package android.content.pm { method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle); method public android.os.ParcelFileDescriptor getShortcutIconFd(android.content.pm.ShortcutInfo); method public android.os.ParcelFileDescriptor getShortcutIconFd(java.lang.String, java.lang.String, android.os.UserHandle); - method public int getShortcutIconResId(android.content.pm.ShortcutInfo); - method public int getShortcutIconResId(java.lang.String, java.lang.String, android.os.UserHandle); method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle); method public boolean hasShortcutHostPermission(); method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle); @@ -10042,7 +10040,9 @@ package android.content.pm { public final class ShortcutInfo implements android.os.Parcelable { method public int describeContents(); method public android.content.ComponentName getActivityComponent(); + method public java.util.List<java.lang.String> getCategories(); 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(); @@ -10066,12 +10066,14 @@ package android.content.pm { field public static final int FLAG_HAS_ICON_RES = 4; // 0x4 field public static final int FLAG_KEY_FIELDS_ONLY = 16; // 0x10 field public static final int FLAG_PINNED = 2; // 0x2 + field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation"; } 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 setCategories(java.util.List<java.lang.String>); 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); @@ -10082,15 +10084,15 @@ package android.content.pm { } public class ShortcutManager { - method public boolean addDynamicShortcut(android.content.pm.ShortcutInfo); - method public void deleteAllDynamicShortcuts(); - method public void deleteDynamicShortcut(java.lang.String); + method public boolean addDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>); method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts(); method public int getIconMaxDimensions(); method public int getMaxDynamicShortcutCount(); method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts(); method public long getRateLimitResetTime(); method public int getRemainingCallCount(); + method public void removeAllDynamicShortcuts(); + method public void removeDynamicShortcuts(java.util.List<java.lang.String>); method public boolean setDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>); method public boolean updateShortcuts(java.util.List<android.content.pm.ShortcutInfo>); } diff --git a/api/system-current.txt b/api/system-current.txt index 56b2f581bb82..80092c0c4316 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -9838,8 +9838,6 @@ package android.content.pm { method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle); method public android.os.ParcelFileDescriptor getShortcutIconFd(android.content.pm.ShortcutInfo); method public android.os.ParcelFileDescriptor getShortcutIconFd(java.lang.String, java.lang.String, android.os.UserHandle); - method public int getShortcutIconResId(android.content.pm.ShortcutInfo); - method public int getShortcutIconResId(java.lang.String, java.lang.String, android.os.UserHandle); method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle); method public boolean hasShortcutHostPermission(); method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle); @@ -10442,7 +10440,9 @@ package android.content.pm { public final class ShortcutInfo implements android.os.Parcelable { method public int describeContents(); method public android.content.ComponentName getActivityComponent(); + method public java.util.List<java.lang.String> getCategories(); 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(); @@ -10466,12 +10466,14 @@ package android.content.pm { field public static final int FLAG_HAS_ICON_RES = 4; // 0x4 field public static final int FLAG_KEY_FIELDS_ONLY = 16; // 0x10 field public static final int FLAG_PINNED = 2; // 0x2 + field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation"; } 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 setCategories(java.util.List<java.lang.String>); 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); @@ -10482,15 +10484,15 @@ package android.content.pm { } public class ShortcutManager { - method public boolean addDynamicShortcut(android.content.pm.ShortcutInfo); - method public void deleteAllDynamicShortcuts(); - method public void deleteDynamicShortcut(java.lang.String); + method public boolean addDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>); method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts(); method public int getIconMaxDimensions(); method public int getMaxDynamicShortcutCount(); method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts(); method public long getRateLimitResetTime(); method public int getRemainingCallCount(); + method public void removeAllDynamicShortcuts(); + method public void removeDynamicShortcuts(java.util.List<java.lang.String>); method public boolean setDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>); method public boolean updateShortcuts(java.util.List<android.content.pm.ShortcutInfo>); } diff --git a/api/test-current.txt b/api/test-current.txt index 679347df0feb..01649f6b1a56 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -9510,8 +9510,6 @@ package android.content.pm { method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle); method public android.os.ParcelFileDescriptor getShortcutIconFd(android.content.pm.ShortcutInfo); method public android.os.ParcelFileDescriptor getShortcutIconFd(java.lang.String, java.lang.String, android.os.UserHandle); - method public int getShortcutIconResId(android.content.pm.ShortcutInfo); - method public int getShortcutIconResId(java.lang.String, java.lang.String, android.os.UserHandle); method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle); method public boolean hasShortcutHostPermission(); method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle); @@ -10053,7 +10051,9 @@ package android.content.pm { public final class ShortcutInfo implements android.os.Parcelable { method public int describeContents(); method public android.content.ComponentName getActivityComponent(); + method public java.util.List<java.lang.String> getCategories(); 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(); @@ -10077,12 +10077,14 @@ package android.content.pm { field public static final int FLAG_HAS_ICON_RES = 4; // 0x4 field public static final int FLAG_KEY_FIELDS_ONLY = 16; // 0x10 field public static final int FLAG_PINNED = 2; // 0x2 + field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation"; } 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 setCategories(java.util.List<java.lang.String>); 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); @@ -10094,15 +10096,15 @@ package android.content.pm { public class ShortcutManager { ctor public ShortcutManager(android.content.Context); - method public boolean addDynamicShortcut(android.content.pm.ShortcutInfo); - method public void deleteAllDynamicShortcuts(); - method public void deleteDynamicShortcut(java.lang.String); + method public boolean addDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>); method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts(); method public int getIconMaxDimensions(); method public int getMaxDynamicShortcutCount(); method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts(); method public long getRateLimitResetTime(); method public int getRemainingCallCount(); + method public void removeAllDynamicShortcuts(); + method public void removeDynamicShortcuts(java.util.List<java.lang.String>); method public boolean setDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>); method public boolean updateShortcuts(java.util.List<android.content.pm.ShortcutInfo>); } diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl index 31d377b554f8..9c90346a075c 100644 --- a/core/java/android/content/pm/IShortcutService.aidl +++ b/core/java/android/content/pm/IShortcutService.aidl @@ -28,11 +28,12 @@ interface IShortcutService { ParceledListSlice getDynamicShortcuts(String packageName, int userId); - boolean addDynamicShortcut(String packageName, in ShortcutInfo shortcutInfo, int userId); + boolean addDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList, + int userId); - void deleteDynamicShortcut(String packageName, in String shortcutId, int userId); + void removeDynamicShortcuts(String packageName, in List shortcutIds, int userId); - void deleteAllDynamicShortcuts(String packageName, int userId); + void removeAllDynamicShortcuts(String packageName, int userId); ParceledListSlice getPinnedShortcuts(String packageName, int userId); diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index a0d2339566a5..4f8650e06bef 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -39,6 +39,7 @@ import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -493,43 +494,24 @@ public class LauncherApps { } /** - * Return the icon resource ID, if {@code shortcut} has one - * (i.e. when {@link ShortcutInfo#hasIconResource()} returns {@code true}). - * - * <p>Callers must be allowed to access the shortcut information, as defined in {@link - * #hasShortcutHostPermission()}. - * - * @param shortcut The target shortcut. + * @hide kept for testing. */ public int getShortcutIconResId(@NonNull ShortcutInfo shortcut) { - return getShortcutIconResId(shortcut.getPackageName(), shortcut.getId(), - shortcut.getUserId()); + return shortcut.getIconResourceId(); } /** - * Return the icon resource ID, if {@code shortcut} has one - * (i.e. when {@link ShortcutInfo#hasIconResource()} returns {@code true}). - * - * <p>Callers must be allowed to access the shortcut information, as defined in {@link - * #hasShortcutHostPermission()}. - * - * @param packageName The target package name. - * @param shortcutId The ID of the shortcut to lad rom. - * @param user The UserHandle of the profile. + * @hide kept for testing. */ public int getShortcutIconResId(@NonNull String packageName, @NonNull String shortcutId, @NonNull UserHandle user) { - return getShortcutIconResId(packageName, shortcutId, user.getIdentifier()); - } + final ShortcutQuery q = new ShortcutQuery(); + q.setPackage(packageName); + q.setShortcutIds(Arrays.asList(shortcutId)); + q.setQueryFlags(ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED); + final List<ShortcutInfo> shortcuts = getShortcuts(q, user); - private int getShortcutIconResId(@NonNull String packageName, @NonNull String shortcutId, - int userId) { - try { - return mService.getShortcutIconResId(mContext.getPackageName(), - packageName, shortcutId, userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return shortcuts.size() > 0 ? shortcuts.get(0).getIconResourceId() : 0; } /** diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 7a807c4cf949..a9000155bc00 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -34,6 +34,8 @@ import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; // TODO Enhance javadoc /** @@ -107,6 +109,11 @@ public final class ShortcutInfo implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface CloneFlags {} + /** + * Shortcut category for + */ + public static final String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation"; + private final String mId; @NonNull @@ -124,6 +131,9 @@ public final class ShortcutInfo implements Parcelable { @Nullable private String mText; + @NonNull + private List<String> mCategories; + /** * Intent *with extras removed*. */ @@ -168,6 +178,7 @@ public final class ShortcutInfo implements Parcelable { mIcon = b.mIcon; mTitle = b.mTitle; mText = b.mText; + mCategories = clone(b.mCategories); mIntent = b.mIntent; if (mIntent != null) { final Bundle intentExtras = mIntent.getExtras(); @@ -181,6 +192,10 @@ public final class ShortcutInfo implements Parcelable { updateTimestamp(); } + private <T> ArrayList<T> clone(List<T> source) { + return (source == null) ? null : new ArrayList<>(source); + } + /** * Throws if any of the mandatory fields is not set. * @@ -202,17 +217,20 @@ public final class ShortcutInfo implements Parcelable { mFlags = source.mFlags; mLastChangedTimestamp = source.mLastChangedTimestamp; + // Just always keep it since it's cheep. + mIconResourceId = source.mIconResourceId; + if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) { mActivityComponent = source.mActivityComponent; if ((cloneFlags & CLONE_REMOVE_ICON) == 0) { mIcon = source.mIcon; mBitmapPath = source.mBitmapPath; - mIconResourceId = source.mIconResourceId; } mTitle = source.mTitle; mText = source.mText; + mCategories = clone(source.mCategories); if ((cloneFlags & CLONE_REMOVE_INTENT) == 0) { mIntent = source.mIntent; mIntentPersistableExtras = source.mIntentPersistableExtras; @@ -262,6 +280,9 @@ public final class ShortcutInfo implements Parcelable { if (source.mText != null) { mText = source.mText; } + if (source.mCategories != null) { + mCategories = clone(source.mCategories); + } if (source.mIntent != null) { mIntent = source.mIntent; mIntentPersistableExtras = source.mIntentPersistableExtras; @@ -325,6 +346,8 @@ public final class ShortcutInfo implements Parcelable { private String mText; + private List<String> mCategories; + private Intent mIntent; private int mWeight; @@ -369,8 +392,9 @@ public final class ShortcutInfo implements Parcelable { * * <p>For performance reasons, icons will <b>NOT</b> be available on instances * returned by {@link ShortcutManager} or {@link LauncherApps}. Launcher applications - * need to use {@link LauncherApps#getShortcutIconFd(ShortcutInfo)} - * and {@link LauncherApps#getShortcutIconResId(ShortcutInfo)}. + * can use {@link ShortcutInfo#getIconResourceId()} if {@link #hasIconResource()} is true. + * Otherwise, if {@link #hasIconFile()} is true, use + * {@link LauncherApps#getShortcutIconFd} to load the image. */ @NonNull public Builder setIcon(Icon icon) { @@ -403,6 +427,18 @@ public final class ShortcutInfo implements Parcelable { } /** + * Sets categories for a shortcut. Launcher applications may use this information to + * categorise shortcuts. + * + * @see #SHORTCUT_CATEGORY_CONVERSATION + */ + @NonNull + public Builder setCategories(List<String> categories) { + mCategories = categories; + return this; + } + + /** * Sets the intent of a shortcut. This is a mandatory field. The extras must only contain * persistable information. (See {@link PersistableBundle}). */ @@ -500,6 +536,14 @@ public final class ShortcutInfo implements Parcelable { } /** + * Return the categories. + */ + @Nullable + public List<String> getCategories() { + return mCategories; + } + + /** * Return the intent. * * <p>All shortcuts must have an intent, but this method will return null when @@ -662,7 +706,9 @@ public final class ShortcutInfo implements Parcelable { mIconResourceId = iconResourceId; } - /** @hide */ + /** + * Get the resource ID for the icon, valid only when {@link #hasIconResource()} } is true. + */ public int getIconResourceId() { return mIconResourceId; } @@ -687,6 +733,8 @@ public final class ShortcutInfo implements Parcelable { mIcon = source.readParcelable(cl); mTitle = source.readString(); mText = source.readString(); + mCategories = new ArrayList<>(); + source.readStringList(mCategories); mIntent = source.readParcelable(cl); mIntentPersistableExtras = source.readParcelable(cl); mWeight = source.readInt(); @@ -706,6 +754,7 @@ public final class ShortcutInfo implements Parcelable { dest.writeParcelable(mIcon, flags); dest.writeString(mTitle); dest.writeString(mText); + dest.writeStringList(mCategories); dest.writeParcelable(mIntent, flags); dest.writeParcelable(mIntentPersistableExtras, flags); dest.writeInt(mWeight); @@ -770,6 +819,9 @@ public final class ShortcutInfo implements Parcelable { sb.append(", text="); sb.append(secure ? "***" : mText); + sb.append(", categories="); + sb.append(mCategories); + sb.append(", icon="); sb.append(mIcon); @@ -807,7 +859,7 @@ public final class ShortcutInfo implements Parcelable { /** @hide */ public ShortcutInfo( @UserIdInt int userId, String id, String packageName, ComponentName activityComponent, - Icon icon, String title, String text, Intent intent, + Icon icon, String title, String text, List<String> categories, Intent intent, PersistableBundle intentPersistableExtras, int weight, PersistableBundle extras, long lastChangedTimestamp, int flags, int iconResId, String bitmapPath) { @@ -818,6 +870,7 @@ public final class ShortcutInfo implements Parcelable { mIcon = icon; mTitle = title; mText = text; + mCategories = clone(categories); mIntent = intent; mIntentPersistableExtras = intentPersistableExtras; mWeight = weight; diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java index 919ccda91ba9..75803d3631a3 100644 --- a/core/java/android/content/pm/ShortcutManager.java +++ b/core/java/android/content/pm/ShortcutManager.java @@ -34,11 +34,11 @@ import java.util.List; * <h3>Dynamic shortcuts and pinned shortcuts</h3> * * An application can publish shortcuts with {@link #setDynamicShortcuts(List)} and - * {@link #addDynamicShortcut(ShortcutInfo)}. There can be at most + * {@link #addDynamicShortcuts(List)}. There can be at most * {@link #getMaxDynamicShortcutCount()} number of dynamic shortcuts at a time from the same * application. - * A dynamic shortcut can be deleted with {@link #deleteDynamicShortcut(String)}, and apps - * can also use {@link #deleteAllDynamicShortcuts()} to delete all dynamic shortcuts. + * A dynamic shortcut can be deleted with {@link #removeDynamicShortcuts(List)}, and apps + * can also use {@link #removeAllDynamicShortcuts()} to delete all dynamic shortcuts. * * <p>The shortcuts that are currently published by the above APIs are called "dynamic", because * they can be removed by the creator application at any time. The user may "pin" dynamic shortcuts @@ -61,11 +61,11 @@ import java.util.List; * * <h3>Rate limiting</h3> * - * Calls to {@link #setDynamicShortcuts(List)}, {@link #addDynamicShortcut(ShortcutInfo)}, + * Calls to {@link #setDynamicShortcuts(List)}, {@link #addDynamicShortcuts(List)}, * and {@link #updateShortcuts(List)} will be * rate-limited. An application can call these methods at most * {@link #getRemainingCallCount()} times until the rate-limiting counter is reset, - * which happens at a certain time every day. + * which happens every hour. * * <p>An application can use {@link #getRateLimitResetTime()} to get the next reset time. * @@ -153,10 +153,10 @@ public class ShortcutManager { * @throws IllegalArgumentException if the caller application has already published the * max number of dynamic shortcuts. */ - public boolean addDynamicShortcut(@NonNull ShortcutInfo shortcutInfo) { + public boolean addDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) { try { - return mService.addDynamicShortcut( - mContext.getPackageName(), shortcutInfo, injectMyUserId()); + return mService.addDynamicShortcuts(mContext.getPackageName(), + new ParceledListSlice(shortcutInfoList), injectMyUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -165,9 +165,10 @@ public class ShortcutManager { /** * Delete a single dynamic shortcut by ID. */ - public void deleteDynamicShortcut(@NonNull String shortcutId) { + public void removeDynamicShortcuts(@NonNull List<String> shortcutIds) { try { - mService.deleteDynamicShortcut(mContext.getPackageName(), shortcutId, injectMyUserId()); + mService.removeDynamicShortcuts(mContext.getPackageName(), shortcutIds, + injectMyUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -176,9 +177,9 @@ public class ShortcutManager { /** * Delete all dynamic shortcuts from the caller application. */ - public void deleteAllDynamicShortcuts() { + public void removeAllDynamicShortcuts() { try { - mService.deleteAllDynamicShortcuts(mContext.getPackageName(), injectMyUserId()); + mService.removeAllDynamicShortcuts(mContext.getPackageName(), injectMyUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 638be5167f86..59a522032b53 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7793,13 +7793,13 @@ public final class Settings { * ShortcutManager specific settings. * This is encoded as a key=value list, separated by commas. Ex: * - * "reset_interval_sec=86400,max_daily_updates=5" + * "reset_interval_sec=86400,max_updates_per_interval=1" * * The following keys are supported: * * <pre> * reset_interval_sec (long) - * max_daily_updates (int) + * max_updates_per_interval (int) * max_icon_dimension_dp (int, DP) * max_icon_dimension_dp_lowram (int, DP) * max_shortcuts (int) diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 58559a53cb5a..b9b65ebc5ff8 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -28,6 +28,7 @@ import android.util.ArraySet; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -37,7 +38,7 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Collection; +import java.util.Arrays; import java.util.List; import java.util.function.Predicate; @@ -51,6 +52,7 @@ class ShortcutPackage extends ShortcutPackageItem { private static final String TAG_INTENT_EXTRAS = "intent-extras"; private static final String TAG_EXTRAS = "extras"; private static final String TAG_SHORTCUT = "shortcut"; + private static final String TAG_CATEGORIES = "categories"; private static final String ATTR_NAME = "name"; private static final String ATTR_DYNAMIC_COUNT = "dynamic-count"; @@ -67,6 +69,11 @@ class ShortcutPackage extends ShortcutPackageItem { private static final String ATTR_ICON_RES = "icon-res"; private static final String ATTR_BITMAP_PATH = "bitmap-path"; + private static final String NAME_CATEGORIES = "categories"; + + private static final String TAG_STRING_ARRAY_XMLUTILS = "string-array"; + private static final String ATTR_NAME_XMLUTILS = "name"; + /** * All the shortcuts from the package, keyed on IDs. */ @@ -305,7 +312,7 @@ class ShortcutPackage extends ShortcutPackageItem { * and return true. Otherwise just return false. */ public boolean tryApiCall(@NonNull ShortcutService s) { - if (getApiCallCount(s) >= s.mMaxDailyUpdates) { + if (getApiCallCount(s) >= s.mMaxUpdatesPerInterval) { return false; } mApiCallCount++; @@ -485,6 +492,16 @@ class ShortcutPackage extends ShortcutPackageItem { ShortcutService.writeAttr(out, ATTR_BITMAP_PATH, si.getBitmapPath()); } + { + final List<String> cat = si.getCategories(); + if (cat != null && cat.size() > 0) { + out.startTag(null, TAG_CATEGORIES); + XmlUtils.writeStringArrayXml(cat.toArray(new String[cat.size()]), + NAME_CATEGORIES, out); + out.endTag(null, TAG_CATEGORIES); + } + } + ShortcutService.writeTagExtra(out, TAG_INTENT_EXTRAS, si.getIntentPersistableExtras()); ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras()); @@ -550,6 +567,7 @@ class ShortcutPackage extends ShortcutPackageItem { int flags; int iconRes; String bitmapPath; + String[] categories = null; id = ShortcutService.parseStringAttribute(parser, ATTR_ID); activityComponent = ShortcutService.parseComponentNameAttribute(parser, @@ -583,11 +601,21 @@ class ShortcutPackage extends ShortcutPackageItem { case TAG_EXTRAS: extras = PersistableBundle.restoreFromXml(parser); continue; + case TAG_CATEGORIES: + // This just contains string-array. + continue; + case TAG_STRING_ARRAY_XMLUTILS: + if (NAME_CATEGORIES.equals(ShortcutService.parseStringAttribute(parser, + ATTR_NAME_XMLUTILS))) { + categories = XmlUtils.readThisStringArrayXml(parser, TAG_STRING_ARRAY_XMLUTILS, null); + } + continue; } throw ShortcutService.throwForInvalidTag(depth, tag); } return new ShortcutInfo( - userId, id, packageName, activityComponent, /* icon =*/ null, title, text, intent, + userId, id, packageName, activityComponent, /* icon =*/ null, title, text, + (categories == null ? null : Arrays.asList(categories)), intent, intentPersistableExtras, weight, extras, lastChangedTimestamp, flags, iconRes, bitmapPath); } diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 1641e1dce169..ac6510ae57ac 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -126,10 +126,10 @@ public class ShortcutService extends IShortcutService.Stub { static final boolean DEBUG_LOAD = false; // STOPSHIP if true @VisibleForTesting - static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day + static final long DEFAULT_RESET_INTERVAL_SEC = 60 * 60; // 1 hour @VisibleForTesting - static final int DEFAULT_MAX_DAILY_UPDATES = 10; + static final int DEFAULT_MAX_UPDATES_PER_INTERVAL = 2; @VisibleForTesting static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 5; @@ -180,7 +180,7 @@ public class ShortcutService extends IShortcutService.Stub { /** * Key name for the max number of modifying API calls per app for every interval. (int) */ - String KEY_MAX_DAILY_UPDATES = "max_daily_updates"; + String KEY_MAX_UPDATES_PER_INTERVAL = "max_updates_per_interval"; /** * Key name for the max icon dimensions in DP, for non-low-memory devices. @@ -232,9 +232,9 @@ public class ShortcutService extends IShortcutService.Stub { private int mMaxDynamicShortcuts; /** - * Max number of updating API calls that each application can make a day. + * Max number of updating API calls that each application can make during the interval. */ - int mMaxDailyUpdates; + int mMaxUpdatesPerInterval; /** * Actual throttling-reset interval. By default it's a day. @@ -426,8 +426,8 @@ public class ShortcutService extends IShortcutService.Stub { ConfigConstants.KEY_RESET_INTERVAL_SEC, DEFAULT_RESET_INTERVAL_SEC) * 1000L); - mMaxDailyUpdates = Math.max(0, (int) parser.getLong( - ConfigConstants.KEY_MAX_DAILY_UPDATES, DEFAULT_MAX_DAILY_UPDATES)); + mMaxUpdatesPerInterval = Math.max(0, (int) parser.getLong( + ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL, DEFAULT_MAX_UPDATES_PER_INTERVAL)); mMaxDynamicShortcuts = Math.max(0, (int) parser.getLong( ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_APP)); @@ -1319,10 +1319,13 @@ public class ShortcutService extends IShortcutService.Stub { } @Override - public boolean addDynamicShortcut(String packageName, ShortcutInfo newShortcut, + public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, @UserIdInt int userId) { verifyCaller(packageName, userId); + final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); + final int size = newShortcuts.size(); + synchronized (mLock) { final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); @@ -1330,12 +1333,15 @@ public class ShortcutService extends IShortcutService.Stub { if (!ps.tryApiCall(this)) { return false; } + for (int i = 0; i < size; i++) { + final ShortcutInfo newShortcut = newShortcuts.get(i); - // Validate the shortcut. - fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false); + // Validate the shortcut. + fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false); - // Add it. - ps.addDynamicShortcut(this, newShortcut); + // Add it. + ps.addDynamicShortcut(this, newShortcut); + } } userPackageChanged(packageName, userId); @@ -1343,19 +1349,22 @@ public class ShortcutService extends IShortcutService.Stub { } @Override - public void deleteDynamicShortcut(String packageName, String shortcutId, + public void removeDynamicShortcuts(String packageName, List shortcutIds, @UserIdInt int userId) { verifyCaller(packageName, userId); - Preconditions.checkStringNotEmpty(shortcutId, "shortcutId must be provided"); + Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided"); synchronized (mLock) { - getPackageShortcutsLocked(packageName, userId).deleteDynamicWithId(this, shortcutId); + for (int i = shortcutIds.size() - 1; i >= 0; i--) { + getPackageShortcutsLocked(packageName, userId).deleteDynamicWithId(this, + Preconditions.checkStringNotEmpty((String) shortcutIds.get(i))); + } } userPackageChanged(packageName, userId); } @Override - public void deleteAllDynamicShortcuts(String packageName, @UserIdInt int userId) { + public void removeAllDynamicShortcuts(String packageName, @UserIdInt int userId) { verifyCaller(packageName, userId); synchronized (mLock) { @@ -1409,7 +1418,7 @@ public class ShortcutService extends IShortcutService.Stub { verifyCaller(packageName, userId); synchronized (mLock) { - return mMaxDailyUpdates + return mMaxUpdatesPerInterval - getPackageShortcutsLocked(packageName, userId).getApiCallCount(this); } } @@ -2079,8 +2088,8 @@ public class ShortcutService extends IShortcutService.Stub { pw.println(mSaveDelayMillis); pw.print(" resetInterval:"); pw.println(mResetInterval); - pw.print(" maxDailyUpdates:"); - pw.println(mMaxDailyUpdates); + pw.print(" maxUpdatesPerInterval:"); + pw.println(mMaxUpdatesPerInterval); pw.print(" maxDynamicShortcuts:"); pw.println(mMaxDynamicShortcuts); pw.println(); @@ -2423,8 +2432,8 @@ public class ShortcutService extends IShortcutService.Stub { } @VisibleForTesting - int getMaxDailyUpdatesForTest() { - return mMaxDailyUpdates; + int getMaxUpdatesPerIntervalForTest() { + return mMaxUpdatesPerInterval; } @VisibleForTesting 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 2fd11da3adf3..ce02a79005ba 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java @@ -216,7 +216,8 @@ public class ShortcutManagerTest extends InstrumentationTestCase { String injectShortcutManagerConstants() { return ConfigConstants.KEY_RESET_INTERVAL_SEC + "=" + (INTERVAL / 1000) + "," + ConfigConstants.KEY_MAX_SHORTCUTS + "=" + MAX_SHORTCUTS + "," - + ConfigConstants.KEY_MAX_DAILY_UPDATES + "=" + MAX_DAILY_UPDATES + "," + + ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL + "=" + + MAX_UPDATES_PER_INTERVAL + "," + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP + "=" + MAX_ICON_DIMENSION + "," + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM + "=" + MAX_ICON_DIMENSION_LOWRAM + "," @@ -465,7 +466,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { private static final int MAX_SHORTCUTS = 10; - private static final int MAX_DAILY_UPDATES = 3; + private static final int MAX_UPDATES_PER_INTERVAL = 3; private static final int MAX_ICON_DIMENSION = 128; @@ -1074,7 +1075,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertResetTimes(START_TIME + INTERVAL, START_TIME + 2 * INTERVAL); - // Advance further; 4 days since start. + // Advance further; 4 hours since start. mInjectedCurrentTimeLillis = START_TIME + 4 * INTERVAL + 50; assertResetTimes(START_TIME + 4 * INTERVAL, START_TIME + 5 * INTERVAL); @@ -1111,14 +1112,14 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mService.updateConfigurationLocked( ConfigConstants.KEY_RESET_INTERVAL_SEC + "=123," + ConfigConstants.KEY_MAX_SHORTCUTS + "=4," - + ConfigConstants.KEY_MAX_DAILY_UPDATES + "=5," + + ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL + "=5," + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP + "=100," + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM + "=50," + ConfigConstants.KEY_ICON_FORMAT + "=WEBP," + ConfigConstants.KEY_ICON_QUALITY + "=75"); assertEquals(123000, mService.getResetIntervalForTest()); assertEquals(4, mService.getMaxDynamicShortcutsForTest()); - assertEquals(5, mService.getMaxDailyUpdatesForTest()); + assertEquals(5, mService.getMaxUpdatesPerIntervalForTest()); assertEquals(100, mService.getMaxIconDimensionForTest()); assertEquals(CompressFormat.WEBP, mService.getIconPersistFormatForTest()); assertEquals(75, mService.getIconPersistQualityForTest()); @@ -1134,8 +1135,8 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertEquals(ShortcutService.DEFAULT_MAX_SHORTCUTS_PER_APP, mService.getMaxDynamicShortcutsForTest()); - assertEquals(ShortcutService.DEFAULT_MAX_DAILY_UPDATES, - mService.getMaxDailyUpdatesForTest()); + assertEquals(ShortcutService.DEFAULT_MAX_UPDATES_PER_INTERVAL, + mService.getMaxUpdatesPerIntervalForTest()); assertEquals(50, mService.getMaxIconDimensionForTest()); @@ -1154,7 +1155,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { /** Test for {@link android.content.pm.ShortcutManager#getRemainingCallCount()} */ public void testGetRemainingCallCount() { - assertEquals(MAX_DAILY_UPDATES, mManager.getRemainingCallCount()); + assertEquals(MAX_UPDATES_PER_INTERVAL, mManager.getRemainingCallCount()); } /** Test for {@link android.content.pm.ShortcutManager#getRateLimitResetTime()} */ @@ -1241,61 +1242,71 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mManager.getDynamicShortcuts()), "shortcut1"); - assertTrue(mManager.addDynamicShortcut(si2)); + assertTrue(mManager.addDynamicShortcuts(list(si2, si3))); assertEquals(1, mManager.getRemainingCallCount()); assertShortcutIds(assertAllNotKeyFieldsOnly( mManager.getDynamicShortcuts()), - "shortcut1", "shortcut2"); + "shortcut1", "shortcut2", "shortcut3"); - // Add with the same ID - assertTrue(mManager.addDynamicShortcut(makeShortcut("shortcut1"))); + // This should not crash. It'll still consume the quota. + assertTrue(mManager.addDynamicShortcuts(list())); assertEquals(0, mManager.getRemainingCallCount()); assertShortcutIds(assertAllNotKeyFieldsOnly( mManager.getDynamicShortcuts()), - "shortcut1", "shortcut2"); + "shortcut1", "shortcut2", "shortcut3"); + + mInjectedCurrentTimeLillis += INTERVAL; // reset + + // Add with the same ID + assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("shortcut1")))); + assertEquals(2, mManager.getRemainingCallCount()); + assertShortcutIds(assertAllNotKeyFieldsOnly( + mManager.getDynamicShortcuts()), + "shortcut1", "shortcut2", "shortcut3"); // TODO Check max number // TODO Check fields. runWithCaller(CALLING_PACKAGE_2, USER_10, () -> { - assertTrue(mManager.addDynamicShortcut(makeShortcut("s1"))); + assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s1")))); }); } - public void testDeleteDynamicShortcut() { + public void testDeleteDynamicShortcuts() { final ShortcutInfo si1 = makeShortcut("shortcut1"); final ShortcutInfo si2 = makeShortcut("shortcut2"); final ShortcutInfo si3 = makeShortcut("shortcut3"); + final ShortcutInfo si4 = makeShortcut("shortcut4"); - assertTrue(mManager.setDynamicShortcuts(list(si1, si2, si3))); + assertTrue(mManager.setDynamicShortcuts(list(si1, si2, si3, si4))); assertShortcutIds(assertAllNotKeyFieldsOnly( mManager.getDynamicShortcuts()), - "shortcut1", "shortcut2", "shortcut3"); + "shortcut1", "shortcut2", "shortcut3", "shortcut4"); assertEquals(2, mManager.getRemainingCallCount()); - mManager.deleteDynamicShortcut("shortcut1"); + mManager.removeDynamicShortcuts(list("shortcut1")); assertShortcutIds(assertAllNotKeyFieldsOnly( mManager.getDynamicShortcuts()), - "shortcut2", "shortcut3"); + "shortcut2", "shortcut3", "shortcut4"); - mManager.deleteDynamicShortcut("shortcut1"); + mManager.removeDynamicShortcuts(list("shortcut1")); assertShortcutIds(assertAllNotKeyFieldsOnly( mManager.getDynamicShortcuts()), - "shortcut2", "shortcut3"); + "shortcut2", "shortcut3", "shortcut4"); - mManager.deleteDynamicShortcut("shortcutXXX"); + mManager.removeDynamicShortcuts(list("shortcutXXX")); assertShortcutIds(assertAllNotKeyFieldsOnly( mManager.getDynamicShortcuts()), - "shortcut2", "shortcut3"); + "shortcut2", "shortcut3", "shortcut4"); - mManager.deleteDynamicShortcut("shortcut2"); + mManager.removeDynamicShortcuts(list("shortcut2", "shortcut4")); assertShortcutIds(assertAllNotKeyFieldsOnly( mManager.getDynamicShortcuts()), "shortcut3"); - mManager.deleteDynamicShortcut("shortcut3"); + mManager.removeDynamicShortcuts(list("shortcut3")); assertShortcutIds(assertAllNotKeyFieldsOnly( mManager.getDynamicShortcuts())); @@ -1315,7 +1326,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertEquals(2, mManager.getRemainingCallCount()); - mManager.deleteAllDynamicShortcuts(); + mManager.removeAllDynamicShortcuts(); assertEquals(0, mManager.getDynamicShortcuts().size()); assertEquals(2, mManager.getRemainingCallCount()); @@ -1383,7 +1394,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertEquals(0, mManager.getRemainingCallCount()); assertEquals(START_TIME + INTERVAL * 2, mManager.getRateLimitResetTime()); - // 4 days later... + // 4 hours later... mInjectedCurrentTimeLillis = START_TIME + 4 * INTERVAL; assertTrue(mManager.setDynamicShortcuts(list(si1))); assertEquals(2, mManager.getRemainingCallCount()); @@ -1791,13 +1802,13 @@ public class ShortcutManagerTest extends InstrumentationTestCase { getCallingUser()); }); runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> { - mManager.deleteDynamicShortcut("s1"); - mManager.deleteDynamicShortcut("s2"); + mManager.removeDynamicShortcuts(list("s1")); + mManager.removeDynamicShortcuts(list("s2")); }); runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> { - mManager.deleteDynamicShortcut("s1"); - mManager.deleteDynamicShortcut("s3"); - mManager.deleteDynamicShortcut("s5"); + mManager.removeDynamicShortcuts(list("s1")); + mManager.removeDynamicShortcuts(list("s3")); + mManager.removeDynamicShortcuts(list("s5")); }); runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> { assertShortcutIds(assertAllDynamic( @@ -2089,7 +2100,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // Delete some. setCaller(CALLING_PACKAGE_1); assertShortcutIds(mManager.getPinnedShortcuts(), "s2"); - mManager.deleteDynamicShortcut("s2"); + mManager.removeDynamicShortcuts(list("s2")); assertShortcutIds(mManager.getPinnedShortcuts(), "s2"); dumpsysOnLogcat(); @@ -2154,19 +2165,19 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // Delete some. runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { assertShortcutIds(mManager.getPinnedShortcuts(), "s2"); - mManager.deleteDynamicShortcut("s2"); + mManager.removeDynamicShortcuts(list("s2")); assertShortcutIds(mManager.getPinnedShortcuts(), "s2"); }); runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { assertShortcutIds(mManager.getPinnedShortcuts(), "s3", "s4"); - mManager.deleteDynamicShortcut("s3"); + mManager.removeDynamicShortcuts(list("s3")); assertShortcutIds(mManager.getPinnedShortcuts(), "s3", "s4"); }); runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { assertShortcutIds(mManager.getPinnedShortcuts() /* none */); - mManager.deleteDynamicShortcut("s2"); + mManager.removeDynamicShortcuts(list("s2")); assertShortcutIds(mManager.getPinnedShortcuts() /* none */); }); @@ -2218,7 +2229,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // Delete some. runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { assertShortcutIds(mManager.getPinnedShortcuts(), "s3"); - mManager.deleteDynamicShortcut("s3"); + mManager.removeDynamicShortcuts(list("s3")); assertShortcutIds(mManager.getPinnedShortcuts(), "s3"); }); @@ -2226,8 +2237,8 @@ public class ShortcutManagerTest extends InstrumentationTestCase { runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { assertShortcutIds(mManager.getPinnedShortcuts(), "s1", "s2"); - mManager.deleteDynamicShortcut("s1"); - mManager.deleteDynamicShortcut("s3"); + mManager.removeDynamicShortcuts(list("s1")); + mManager.removeDynamicShortcuts(list("s3")); assertShortcutIds(mManager.getPinnedShortcuts(), "s1", "s2"); }); @@ -2336,13 +2347,13 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // Delete all dynamic. runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { - mManager.deleteAllDynamicShortcuts(); + mManager.removeAllDynamicShortcuts(); assertEquals(0, mManager.getDynamicShortcuts().size()); assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()), "s1", "s2", "s3"); }); runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { - mManager.deleteAllDynamicShortcuts(); + mManager.removeAllDynamicShortcuts(); assertEquals(0, mManager.getDynamicShortcuts().size()); assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()), "s2", "s1"); @@ -2377,7 +2388,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { }); // Re-publish s1. runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { - assertTrue(mManager.addDynamicShortcut(makeShortcut("s1"))); + assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s1")))); assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()), "s1"); assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()), "s1", "s2", "s3"); @@ -2979,7 +2990,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // Just to make it complicated, delete some. setCaller(CALLING_PACKAGE_1); - mManager.deleteDynamicShortcut("s2"); + mManager.removeDynamicShortcuts(list("s2")); // intent and check. setCaller(LAUNCHER_1); @@ -3051,11 +3062,11 @@ public class ShortcutManagerTest extends InstrumentationTestCase { any(UserHandle.class) ); - // Test for addDynamicShortcut. + // Test for addDynamicShortcuts. reset(c0); runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> { - dumpsysOnLogcat("before addDynamicShortcut"); - assertTrue(mManager.addDynamicShortcut(makeShortcut("s4"))); + dumpsysOnLogcat("before addDynamicShortcuts"); + assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s4")))); }); waitOnMainThread(); @@ -3071,7 +3082,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // Test for remove reset(c0); runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> { - mManager.deleteDynamicShortcut("s1"); + mManager.removeDynamicShortcuts(list("s1")); }); waitOnMainThread(); @@ -3104,7 +3115,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // Test for deleteAll reset(c0); runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> { - mManager.deleteAllDynamicShortcuts(); + mManager.removeAllDynamicShortcuts(); }); waitOnMainThread(); @@ -3178,7 +3189,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { resetAll(all); runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { - mManager.deleteDynamicShortcut("x"); + mManager.removeDynamicShortcuts(list()); }); waitOnMainThread(); @@ -3195,7 +3206,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { resetAll(all); runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { - mManager.deleteDynamicShortcut("x"); + mManager.removeDynamicShortcuts(list()); }); waitOnMainThread(); @@ -3213,7 +3224,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { resetAll(all); runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { - mManager.deleteDynamicShortcut("x"); + mManager.removeDynamicShortcuts(list()); }); waitOnMainThread(); @@ -3233,7 +3244,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { resetAll(all); runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { - mManager.deleteDynamicShortcut("x"); + mManager.removeDynamicShortcuts(list()); }); waitOnMainThread(); @@ -3253,7 +3264,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { resetAll(all); runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { - mManager.deleteDynamicShortcut("x"); + mManager.removeDynamicShortcuts(list()); }); waitOnMainThread(); @@ -3477,16 +3488,16 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // Remove all dynamic shortcuts; now all shortcuts are just pinned. runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { - mManager.deleteAllDynamicShortcuts(); + mManager.removeAllDynamicShortcuts(); }); runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { - mManager.deleteAllDynamicShortcuts(); + mManager.removeAllDynamicShortcuts(); }); runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { - mManager.deleteAllDynamicShortcuts(); + mManager.removeAllDynamicShortcuts(); }); runWithCaller(CALLING_PACKAGE_2, USER_10, () -> { - mManager.deleteAllDynamicShortcuts(); + mManager.removeAllDynamicShortcuts(); }); @@ -4009,22 +4020,22 @@ public class ShortcutManagerTest extends InstrumentationTestCase { public void testHandlePackageDelete() { setCaller(CALLING_PACKAGE_1, USER_0); - assertTrue(mManager.addDynamicShortcut(makeShortcut("s1"))); + assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s1")))); setCaller(CALLING_PACKAGE_2, USER_0); - assertTrue(mManager.addDynamicShortcut(makeShortcut("s1"))); + assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s1")))); setCaller(CALLING_PACKAGE_3, USER_0); - assertTrue(mManager.addDynamicShortcut(makeShortcut("s1"))); + assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s1")))); setCaller(CALLING_PACKAGE_1, USER_10); - assertTrue(mManager.addDynamicShortcut(makeShortcut("s1"))); + assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s1")))); setCaller(CALLING_PACKAGE_2, USER_10); - assertTrue(mManager.addDynamicShortcut(makeShortcut("s1"))); + assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s1")))); setCaller(CALLING_PACKAGE_3, USER_10); - assertTrue(mManager.addDynamicShortcut(makeShortcut("s1"))); + assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s1")))); assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0)); assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0)); @@ -4980,6 +4991,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { .setTitle("title") .setText("text") .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val")) + .setCategories(list(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz")) .setWeight(123) .setExtras(pb) .build(); @@ -4995,6 +5007,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertEquals("content://a.b.c/", si.getIcon().getUriString()); assertEquals("title", si.getTitle()); assertEquals("text", si.getText()); + assertEquals(list(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories()); assertEquals("action", si.getIntent().getAction()); assertEquals("val", si.getIntent().getStringExtra("key")); assertEquals(123, si.getWeight()); @@ -5016,6 +5029,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { .setIcon(Icon.createWithContentUri("content://a.b.c/")) .setTitle("title") .setText("text") + .setCategories(list(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz")) .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val")) .setWeight(123) .setExtras(pb) @@ -5034,6 +5048,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertEquals("content://a.b.c/", si.getIcon().getUriString()); assertEquals("title", si.getTitle()); assertEquals("text", si.getText()); + assertEquals(list(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories()); assertEquals("action", si.getIntent().getAction()); assertEquals("val", si.getIntent().getStringExtra("key")); assertEquals(123, si.getWeight()); @@ -5051,6 +5066,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertEquals(null, si.getIcon()); assertEquals("title", si.getTitle()); assertEquals("text", si.getText()); + assertEquals(list(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories()); assertEquals("action", si.getIntent().getAction()); assertEquals("val", si.getIntent().getStringExtra("key")); assertEquals(123, si.getWeight()); @@ -5058,7 +5074,8 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags()); assertEquals(null, si.getBitmapPath()); - assertEquals(0, si.getIconResourceId()); + + assertEquals(456, si.getIconResourceId()); si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER); @@ -5068,13 +5085,15 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertEquals(null, si.getIcon()); assertEquals("title", si.getTitle()); assertEquals("text", si.getText()); + assertEquals(list(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories()); assertEquals(null, si.getIntent()); assertEquals(123, si.getWeight()); assertEquals(1, si.getExtras().getInt("k")); assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags()); assertEquals(null, si.getBitmapPath()); - assertEquals(0, si.getIconResourceId()); + + assertEquals(456, si.getIconResourceId()); si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); @@ -5084,13 +5103,56 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertEquals(null, si.getIcon()); assertEquals(null, si.getTitle()); assertEquals(null, si.getText()); + assertEquals(null, si.getCategories()); assertEquals(null, si.getIntent()); assertEquals(0, si.getWeight()); assertEquals(null, si.getExtras()); assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_KEY_FIELDS_ONLY, si.getFlags()); assertEquals(null, si.getBitmapPath()); - assertEquals(0, si.getIconResourceId()); + + assertEquals(456, si.getIconResourceId()); + } + + public void testShortcutInfoClone_minimum() { + PersistableBundle pb = new PersistableBundle(); + pb.putInt("k", 1); + ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext()) + .setId("id") + .setTitle("title") + .setIntent(makeIntent("action", ShortcutActivity.class)) + .build(); + ShortcutInfo si = sorig.clone(/* clone flags*/ 0); + + assertEquals(getTestContext().getPackageName(), si.getPackageName()); + assertEquals("id", si.getId()); + assertEquals("title", si.getTitle()); + assertEquals("action", si.getIntent().getAction()); + assertEquals(null, si.getCategories()); + + si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR); + + assertEquals(getTestContext().getPackageName(), si.getPackageName()); + assertEquals("id", si.getId()); + assertEquals("title", si.getTitle()); + assertEquals("action", si.getIntent().getAction()); + assertEquals(null, si.getCategories()); + + si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER); + + assertEquals(getTestContext().getPackageName(), si.getPackageName()); + assertEquals("id", si.getId()); + assertEquals("title", si.getTitle()); + assertEquals(null, si.getIntent()); + assertEquals(null, si.getCategories()); + + si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); + + assertEquals(getTestContext().getPackageName(), si.getPackageName()); + assertEquals("id", si.getId()); + assertEquals(null, si.getTitle()); + assertEquals(null, si.getIntent()); + assertEquals(null, si.getCategories()); } public void testShortcutInfoCopyNonNullFieldsFrom() throws InterruptedException { @@ -5102,6 +5164,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { .setIcon(Icon.createWithContentUri("content://a.b.c/")) .setTitle("title") .setText("text") + .setCategories(list(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz")) .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val")) .setWeight(123) .setExtras(pb) @@ -5115,38 +5178,57 @@ public class ShortcutManagerTest extends InstrumentationTestCase { si = sorig.clone(/* flags=*/ 0); si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") .setActivityComponent(new ComponentName("x", "y")).build()); + assertEquals("text", si.getText()); assertEquals(new ComponentName("x", "y"), si.getActivityComponent()); si = sorig.clone(/* flags=*/ 0); si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") .setIcon(Icon.createWithContentUri("content://x.y.z/")).build()); + assertEquals("text", si.getText()); assertEquals("content://x.y.z/", si.getIcon().getUriString()); si = sorig.clone(/* flags=*/ 0); si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") .setTitle("xyz").build()); + assertEquals("text", si.getText()); assertEquals("xyz", si.getTitle()); si = sorig.clone(/* flags=*/ 0); si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") .setText("xxx").build()); + assertEquals(123, si.getWeight()); assertEquals("xxx", si.getText()); si = sorig.clone(/* flags=*/ 0); si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") + .setCategories(list()).build()); + assertEquals("text", si.getText()); + assertEquals(list(), si.getCategories()); + + si = sorig.clone(/* flags=*/ 0); + si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") + .setCategories(list("x")).build()); + assertEquals("text", si.getText()); + assertEquals(list("x"), si.getCategories()); + + si = sorig.clone(/* flags=*/ 0); + si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") .setIntent(makeIntent("action2", ShortcutActivity.class)).build()); + assertEquals("text", si.getText()); assertEquals("action2", si.getIntent().getAction()); assertEquals(null, si.getIntent().getStringExtra("key")); si = sorig.clone(/* flags=*/ 0); si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") .setIntent(makeIntent("action3", ShortcutActivity.class, "key", "x")).build()); + assertEquals("text", si.getText()); assertEquals("action3", si.getIntent().getAction()); assertEquals("x", si.getIntent().getStringExtra("key")); si = sorig.clone(/* flags=*/ 0); si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") .setWeight(999).build()); + assertEquals("text", si.getText()); assertEquals(999, si.getWeight()); @@ -5156,8 +5238,11 @@ public class ShortcutManagerTest extends InstrumentationTestCase { si = sorig.clone(/* flags=*/ 0); si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") .setExtras(pb2).build()); + assertEquals("text", si.getText()); assertEquals(99, si.getExtras().getInt("x")); + // Make sure the timestamp gets updated too. + final long timestamp = si.getLastChangedTimestamp(); Thread.sleep(2); @@ -5181,12 +5266,13 @@ public class ShortcutManagerTest extends InstrumentationTestCase { .setIcon(bmp32x32) .setTitle("title") .setText("text") + .setCategories(list(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz")) .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val")) .setWeight(123) .setExtras(pb) .build(); - mManager.addDynamicShortcut(sorig); + mManager.addDynamicShortcuts(list(sorig)); Thread.sleep(2); final long now = System.currentTimeMillis(); @@ -5207,6 +5293,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertEquals(null, si.getIcon()); assertEquals("title", si.getTitle()); assertEquals("text", si.getText()); + assertEquals(list(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories()); assertEquals("action", si.getIntent().getAction()); assertEquals("val", si.getIntent().getStringExtra("key")); assertEquals(123, si.getWeight()); @@ -5232,12 +5319,13 @@ public class ShortcutManagerTest extends InstrumentationTestCase { .setIcon(bmp32x32) .setTitle("title") .setText("text") + .setCategories(list(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz")) .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val")) .setWeight(123) .setExtras(pb) .build(); - mManager.addDynamicShortcut(sorig); + mManager.addDynamicShortcuts(list(sorig)); // Dynamic shortcuts won't be backed up, so we need to pin it. setCaller(LAUNCHER_1, USER_0); @@ -5246,6 +5334,8 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // Do backup & restore. backupAndRestore(); + mService.handleUnlockUser(USER_0); // Load user-0. + ShortcutInfo si; si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_0); @@ -5255,6 +5345,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertEquals(null, si.getIcon()); assertEquals("title", si.getTitle()); assertEquals("text", si.getText()); + assertEquals(list(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories()); assertEquals("action", si.getIntent().getAction()); assertEquals("val", si.getIntent().getStringExtra("key")); assertEquals(123, si.getWeight()); |