diff options
| author | 2016-06-08 16:59:59 +0000 | |
|---|---|---|
| committer | 2016-06-08 17:00:00 +0000 | |
| commit | 3bb436600654e4da253a4b77a86c1dbfa208da8e (patch) | |
| tree | e3ed06d667caae82f0154d8962d68a8cf36f4bf1 | |
| parent | d05236024edf0ebcc5c55566e6334a49a8a83ebd (diff) | |
| parent | 157b1628fd84dc3ef0355fddd8d281618f94d33e (diff) | |
Merge "ShortcutManager: deal with changing resource IDs on app update" into nyc-mr1-dev
| -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/ShortcutInfo.java | 381 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/ShortcutPackage.java | 102 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/ShortcutParser.java | 6 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/ShortcutService.java | 90 | ||||
| -rw-r--r-- | services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java | 79 | ||||
| -rw-r--r-- | services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java | 222 | ||||
| -rw-r--r-- | services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java | 189 |
10 files changed, 956 insertions, 149 deletions
diff --git a/api/current.txt b/api/current.txt index 83f6131b875e..cb42b86d7ffe 100644 --- a/api/current.txt +++ b/api/current.txt @@ -10088,19 +10088,7 @@ package android.content.pm { 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 - field public static final int CLONE_REMOVE_FOR_LAUNCHER = 3; // 0x3 - field public static final int CLONE_REMOVE_NON_KEY_INFO = 4; // 0x4 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_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"; } diff --git a/api/system-current.txt b/api/system-current.txt index e2a051612bd7..8cb9f0d44df2 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -10511,19 +10511,7 @@ package android.content.pm { 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 - field public static final int CLONE_REMOVE_FOR_LAUNCHER = 3; // 0x3 - field public static final int CLONE_REMOVE_NON_KEY_INFO = 4; // 0x4 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_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"; } diff --git a/api/test-current.txt b/api/test-current.txt index e74b84505731..d20f87ae1a55 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -10101,19 +10101,7 @@ package android.content.pm { 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 - field public static final int CLONE_REMOVE_FOR_LAUNCHER = 3; // 0x3 - field public static final int CLONE_REMOVE_NON_KEY_INFO = 4; // 0x4 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_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"; } diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 28e78879e2a5..da58717a46ef 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -22,8 +22,8 @@ import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; +import android.content.res.Resources.NotFoundException; import android.graphics.drawable.Icon; import android.os.Bundle; import android.os.Parcel; @@ -31,7 +31,9 @@ import android.os.Parcelable; import android.os.PersistableBundle; import android.os.UserHandle; import android.util.ArraySet; +import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; @@ -54,31 +56,37 @@ import java.util.Set; * @see {@link ShortcutManager}. */ public final class ShortcutInfo implements Parcelable { - /* @hide */ + static final String TAG = "Shortcut"; + + private static final String RES_TYPE_STRING = "string"; + + private static final String ANDROID_PACKAGE_NAME = "android"; + + /** @hide */ public static final int FLAG_DYNAMIC = 1 << 0; - /* @hide */ + /** @hide */ public static final int FLAG_PINNED = 1 << 1; - /* @hide */ + /** @hide */ public static final int FLAG_HAS_ICON_RES = 1 << 2; - /* @hide */ + /** @hide */ public static final int FLAG_HAS_ICON_FILE = 1 << 3; - /* @hide */ + /** @hide */ public static final int FLAG_KEY_FIELDS_ONLY = 1 << 4; - /* @hide */ + /** @hide */ public static final int FLAG_MANIFEST = 1 << 5; - /* @hide */ + /** @hide */ public static final int FLAG_DISABLED = 1 << 6; - /* @hide */ + /** @hide */ public static final int FLAG_STRINGS_RESOLVED = 1 << 7; - /* @hide */ + /** @hide */ public static final int FLAG_IMMUTABLE = 1 << 8; /** @hide */ @@ -99,20 +107,24 @@ public final class ShortcutInfo implements Parcelable { // Cloning options. - /* @hide */ + /** @hide */ private static final int CLONE_REMOVE_ICON = 1 << 0; - /* @hide */ + /** @hide */ private static final int CLONE_REMOVE_INTENT = 1 << 1; - /* @hide */ + /** @hide */ public static final int CLONE_REMOVE_NON_KEY_INFO = 1 << 2; - /* @hide */ - public static final int CLONE_REMOVE_FOR_CREATOR = CLONE_REMOVE_ICON; + /** @hide */ + public static final int CLONE_REMOVE_RES_NAMES = 1 << 3; + + /** @hide */ + public static final int CLONE_REMOVE_FOR_CREATOR = CLONE_REMOVE_ICON | CLONE_REMOVE_RES_NAMES; - /* @hide */ - public static final int CLONE_REMOVE_FOR_LAUNCHER = CLONE_REMOVE_ICON | CLONE_REMOVE_INTENT; + /** @hide */ + public static final int CLONE_REMOVE_FOR_LAUNCHER = CLONE_REMOVE_ICON | CLONE_REMOVE_INTENT + | CLONE_REMOVE_RES_NAMES; /** @hide */ @IntDef(flag = true, @@ -120,6 +132,7 @@ public final class ShortcutInfo implements Parcelable { CLONE_REMOVE_ICON, CLONE_REMOVE_INTENT, CLONE_REMOVE_NON_KEY_INFO, + CLONE_REMOVE_RES_NAMES, CLONE_REMOVE_FOR_CREATOR, CLONE_REMOVE_FOR_LAUNCHER }) @@ -144,16 +157,22 @@ public final class ShortcutInfo implements Parcelable { private int mTitleResId; + private String mTitleResName; + @Nullable private CharSequence mTitle; private int mTextResId; + private String mTextResName; + @Nullable private CharSequence mText; private int mDisabledMessageResId; + private String mDisabledMessageResName; + @Nullable private CharSequence mDisabledMessage; @@ -184,7 +203,9 @@ public final class ShortcutInfo implements Parcelable { private int mFlags; // Internal use only. - private int mIconResourceId; + private int mIconResId; + + private String mIconResName; // Internal use only. @Nullable @@ -251,7 +272,7 @@ public final class ShortcutInfo implements Parcelable { mLastChangedTimestamp = source.mLastChangedTimestamp; // Just always keep it since it's cheep. - mIconResourceId = source.mIconResourceId; + mIconResId = source.mIconResId; if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) { mActivity = source.mActivity; @@ -274,37 +295,235 @@ public final class ShortcutInfo implements Parcelable { } mRank = source.mRank; mExtras = source.mExtras; + + if ((cloneFlags & CLONE_REMOVE_RES_NAMES) == 0) { + mTitleResName = source.mTitleResName; + mTextResName = source.mTextResName; + mDisabledMessageResName = source.mDisabledMessageResName; + mIconResName = source.mIconResName; + } } else { // Set this bit. mFlags |= FLAG_KEY_FIELDS_ONLY; } } - /** @hide */ - public void resolveStringsRequiringCrossUser(Context context) throws NameNotFoundException { + /** + * Load a string resource from the publisher app. + * + * @param resId resource ID + * @param defValue default value to be returned when the specified resource isn't found. + */ + private CharSequence getResourceString(Resources res, int resId, CharSequence defValue) { + try { + return res.getString(resId); + } catch (NotFoundException e) { + Log.e(TAG, "Resource for ID=" + resId + " not found in package " + mPackageName); + return defValue; + } + } + + /** + * Load the string resources for the text fields and set them to the actual value fields. + * This will set {@link #FLAG_STRINGS_RESOLVED}. + * + * @param res {@link Resources} for the publisher. Must have been loaded with + * {@link PackageManager#getResourcesForApplicationAsUser}. + * + * @hide + */ + public void resolveResourceStrings(@NonNull Resources res) { mFlags |= FLAG_STRINGS_RESOLVED; if ((mTitleResId == 0) && (mTextResId == 0) && (mDisabledMessageResId == 0)) { return; // Bail early. } - final Resources res = context.getPackageManager().getResourcesForApplicationAsUser( - mPackageName, mUserId); if (mTitleResId != 0) { - mTitle = res.getString(mTitleResId); - mTitleResId = 0; + mTitle = getResourceString(res, mTitleResId, mTitle); } if (mTextResId != 0) { - mText = res.getString(mTextResId); - mTextResId = 0; + mText = getResourceString(res, mTextResId, mText); } if (mDisabledMessageResId != 0) { - mDisabledMessage = res.getString(mDisabledMessageResId); - mDisabledMessageResId = 0; + mDisabledMessage = getResourceString(res, mDisabledMessageResId, mDisabledMessage); + } + } + + /** + * Look up resource name for a given resource ID. + * + * @return a simple resource name (e.g. "text_1") when {@code withType} is false, or with the + * type (e.g. "string/text_1"). + * + * @hide + */ + @VisibleForTesting + public static String lookUpResourceName(@NonNull Resources res, int resId, boolean withType, + @NonNull String packageName) { + if (resId == 0) { + return null; + } + try { + final String fullName = res.getResourceName(resId); + + if (ANDROID_PACKAGE_NAME.equals(getResourcePackageName(fullName))) { + // If it's a framework resource, the value won't change, so just return the ID + // value as a string. + return String.valueOf(resId); + } + return withType ? getResourceTypeAndEntryName(fullName) + : getResourceEntryName(fullName); + } catch (NotFoundException e) { + Log.e(TAG, "Resource name for ID=" + resId + " not found in package " + packageName + + ". Resource IDs may change when the application is upgraded, and the system" + + " may not be able to find the correct resource."); + return null; + } + } + + /** + * Extract the package name from a fully-donated resource name. + * e.g. "com.android.app1:drawable/icon1" -> "com.android.app1" + * @hide + */ + @VisibleForTesting + public static String getResourcePackageName(@NonNull String fullResourceName) { + final int p1 = fullResourceName.indexOf(':'); + if (p1 < 0) { + return null; + } + return fullResourceName.substring(0, p1); + } + + /** + * Extract the type name from a fully-donated resource name. + * e.g. "com.android.app1:drawable/icon1" -> "drawable" + * @hide + */ + @VisibleForTesting + public static String getResourceTypeName(@NonNull String fullResourceName) { + final int p1 = fullResourceName.indexOf(':'); + if (p1 < 0) { + return null; + } + final int p2 = fullResourceName.indexOf('/', p1 + 1); + if (p2 < 0) { + return null; + } + return fullResourceName.substring(p1 + 1, p2); + } + + /** + * Extract the type name + the entry name from a fully-donated resource name. + * e.g. "com.android.app1:drawable/icon1" -> "drawable/icon1" + * @hide + */ + @VisibleForTesting + public static String getResourceTypeAndEntryName(@NonNull String fullResourceName) { + final int p1 = fullResourceName.indexOf(':'); + if (p1 < 0) { + return null; + } + return fullResourceName.substring(p1 + 1); + } + + /** + * Extract the entry name from a fully-donated resource name. + * e.g. "com.android.app1:drawable/icon1" -> "icon1" + * @hide + */ + @VisibleForTesting + public static String getResourceEntryName(@NonNull String fullResourceName) { + final int p1 = fullResourceName.indexOf('/'); + if (p1 < 0) { + return null; + } + return fullResourceName.substring(p1 + 1); + } + + /** + * Return the resource ID for a given resource ID. + * + * Basically its' a wrapper over {@link Resources#getIdentifier(String, String, String)}, except + * if {@code resourceName} is an integer then it'll just return its value. (Which also the + * aforementioned method would do internally, but not documented, so doing here explicitly.) + * + * @param res {@link Resources} for the publisher. Must have been loaded with + * {@link PackageManager#getResourcesForApplicationAsUser}. + * + * @hide + */ + @VisibleForTesting + public static int lookUpResourceId(@NonNull Resources res, @Nullable String resourceName, + @Nullable String resourceType, String packageName) { + if (resourceName == null) { + return 0; + } + try { + try { + // It the name can be parsed as an integer, just use it. + return Integer.parseInt(resourceName); + } catch (NumberFormatException ignore) { + } + + return res.getIdentifier(resourceName, resourceType, packageName); + } catch (NotFoundException e) { + Log.e(TAG, "Resource ID for name=" + resourceName + " not found in package " + + packageName); + return 0; } } /** + * Look up resource names from the resource IDs for the icon res and the text fields, and fill + * in the resource name fields. + * + * @param res {@link Resources} for the publisher. Must have been loaded with + * {@link PackageManager#getResourcesForApplicationAsUser}. + * + * @hide + */ + public void lookupAndFillInResourceNames(@NonNull Resources res) { + if ((mTitleResId == 0) && (mTextResId == 0) && (mDisabledMessageResId == 0) + && (mIconResId == 0)) { + return; // Bail early. + } + + // We don't need types for strings because their types are always "string". + mTitleResName = lookUpResourceName(res, mTitleResId, /*withType=*/ false, mPackageName); + mTextResName = lookUpResourceName(res, mTextResId, /*withType=*/ false, mPackageName); + mDisabledMessageResName = lookUpResourceName(res, mDisabledMessageResId, + /*withType=*/ false, mPackageName); + + // But icons have multiple possible types, so include the type. + mIconResName = lookUpResourceName(res, mIconResId, /*withType=*/ true, mPackageName); + } + + /** + * Look up resource IDs from the resource names for the icon res and the text fields, and fill + * in the resource ID fields. + * + * This is called when an app is updated. + * + * @hide + */ + public void lookupAndFillInResourceIds(@NonNull Resources res) { + if ((mTitleResName == null) && (mTextResName == null) && (mDisabledMessageResName == null) + && (mIconResName == null)) { + return; // Bail early. + } + + mTitleResId = lookUpResourceId(res, mTitleResName, RES_TYPE_STRING, mPackageName); + mTextResId = lookUpResourceId(res, mTextResName, RES_TYPE_STRING, mPackageName); + mDisabledMessageResId = lookUpResourceId(res, mDisabledMessageResName, RES_TYPE_STRING, + mPackageName); + + // mIconResName already contains the type, so the third argument is not needed. + mIconResId = lookUpResourceId(res, mIconResName, null, mPackageName); + } + + /** * Copy a {@link ShortcutInfo}, optionally removing fields. * @hide */ @@ -344,27 +563,38 @@ public final class ShortcutInfo implements Parcelable { if (source.mIcon != null) { mIcon = source.mIcon; + + mIconResId = 0; + mIconResName = null; + mBitmapPath = null; } if (source.mTitle != null) { mTitle = source.mTitle; mTitleResId = 0; + mTitleResName = null; } else if (source.mTitleResId != 0) { mTitle = null; mTitleResId = source.mTitleResId; + mTitleResName = null; } + if (source.mText != null) { mText = source.mText; mTextResId = 0; + mTextResName = null; } else if (source.mTextResId != 0) { mText = null; mTextResId = source.mTextResId; + mTextResName = null; } if (source.mDisabledMessage != null) { mDisabledMessage = source.mDisabledMessage; mDisabledMessageResId = 0; + mDisabledMessageResName = null; } else if (source.mDisabledMessageResId != 0) { mDisabledMessage = null; mDisabledMessageResId = source.mDisabledMessageResId; + mDisabledMessageResName = null; } if (source.mCategories != null) { mCategories = clone(source.mCategories); @@ -983,14 +1213,17 @@ public final class ShortcutInfo implements Parcelable { /** @hide */ public void setIconResourceId(int iconResourceId) { - mIconResourceId = iconResourceId; + if (mIconResId != iconResourceId) { + mIconResName = null; + } + mIconResId = iconResourceId; } /** * Get the resource ID for the icon, valid only when {@link #hasIconResource()} } is true. */ public int getIconResourceId() { - return mIconResourceId; + return mIconResId; } /** @hide */ @@ -1005,6 +1238,9 @@ public final class ShortcutInfo implements Parcelable { /** @hide */ public void setDisabledMessageResId(int disabledMessageResId) { + if (mDisabledMessageResId != disabledMessageResId) { + mDisabledMessageResName = null; + } mDisabledMessageResId = disabledMessageResId; mDisabledMessage = null; } @@ -1013,6 +1249,47 @@ public final class ShortcutInfo implements Parcelable { public void setDisabledMessage(String disabledMessage) { mDisabledMessage = disabledMessage; mDisabledMessageResId = 0; + mDisabledMessageResName = null; + } + + /** @hide */ + public String getTitleResName() { + return mTitleResName; + } + + /** @hide */ + public void setTitleResName(String titleResName) { + mTitleResName = titleResName; + } + + /** @hide */ + public String getTextResName() { + return mTextResName; + } + + /** @hide */ + public void setTextResName(String textResName) { + mTextResName = textResName; + } + + /** @hide */ + public String getDisabledMessageResName() { + return mDisabledMessageResName; + } + + /** @hide */ + public void setDisabledMessageResName(String disabledMessageResName) { + mDisabledMessageResName = disabledMessageResName; + } + + /** @hide */ + public String getIconResName() { + return mIconResName; + } + + /** @hide */ + public void setIconResName(String iconResName) { + mIconResName = iconResName; } private ShortcutInfo(Parcel source) { @@ -1035,9 +1312,14 @@ public final class ShortcutInfo implements Parcelable { mExtras = source.readParcelable(cl); mLastChangedTimestamp = source.readLong(); mFlags = source.readInt(); - mIconResourceId = source.readInt(); + mIconResId = source.readInt(); mBitmapPath = source.readString(); + mIconResName = source.readString(); + mTitleResName = source.readString(); + mTextResName = source.readString(); + mDisabledMessageResName = source.readString(); + int N = source.readInt(); if (N == 0) { mCategories = null; @@ -1069,9 +1351,14 @@ public final class ShortcutInfo implements Parcelable { dest.writeParcelable(mExtras, flags); dest.writeLong(mLastChangedTimestamp); dest.writeInt(mFlags); - dest.writeInt(mIconResourceId); + dest.writeInt(mIconResId); dest.writeString(mBitmapPath); + dest.writeString(mIconResName); + dest.writeString(mTitleResName); + dest.writeString(mTextResName); + dest.writeString(mDisabledMessageResName); + if (mCategories != null) { final int N = mCategories.size(); dest.writeInt(N); @@ -1160,16 +1447,25 @@ public final class ShortcutInfo implements Parcelable { sb.append(secure ? "***" : mTitle); sb.append(", resId="); sb.append(mTitleResId); + sb.append("["); + sb.append(mTitleResName); + sb.append("]"); sb.append(", longLabel="); sb.append(secure ? "***" : mText); sb.append(", resId="); sb.append(mTextResId); + sb.append("["); + sb.append(mTextResName); + sb.append("]"); sb.append(", disabledMessage="); sb.append(secure ? "***" : mDisabledMessage); sb.append(", resId="); sb.append(mDisabledMessageResId); + sb.append("["); + sb.append(mDisabledMessageResName); + sb.append("]"); sb.append(", categories="); sb.append(mCategories); @@ -1195,7 +1491,10 @@ public final class ShortcutInfo implements Parcelable { if (includeInternalData) { sb.append(", iconRes="); - sb.append(mIconResourceId); + sb.append(mIconResId); + sb.append("["); + sb.append(mIconResName); + sb.append("]"); sb.append(", bitmapPath="); sb.append(mBitmapPath); @@ -1208,11 +1507,13 @@ public final class ShortcutInfo implements Parcelable { /** @hide */ public ShortcutInfo( @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, + Icon icon, CharSequence title, int titleResId, String titleResName, + CharSequence text, int textResId, String textResName, + CharSequence disabledMessage, int disabledMessageResId, String disabledMessageResName, + Set<String> categories, Intent intent, PersistableBundle intentPersistableExtras, int rank, PersistableBundle extras, long lastChangedTimestamp, - int flags, int iconResId, String bitmapPath) { + int flags, int iconResId, String iconResName, String bitmapPath) { mUserId = userId; mId = id; mPackageName = packageName; @@ -1220,10 +1521,13 @@ public final class ShortcutInfo implements Parcelable { mIcon = icon; mTitle = title; mTitleResId = titleResId; + mTitleResName = titleResName; mText = text; mTextResId = textResId; + mTextResName = textResName; mDisabledMessage = disabledMessage; mDisabledMessageResId = disabledMessageResId; + mDisabledMessageResName = disabledMessageResName; mCategories = clone(categories); mIntent = intent; mIntentPersistableExtras = intentPersistableExtras; @@ -1231,7 +1535,8 @@ public final class ShortcutInfo implements Parcelable { mExtras = extras; mLastChangedTimestamp = lastChangedTimestamp; mFlags = flags; - mIconResourceId = iconResId; + mIconResId = iconResId; + mIconResName = iconResName; mBitmapPath = bitmapPath; } } diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index e0c28fa46756..f336ff32c7da 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -19,9 +19,12 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ShortcutInfo; +import android.content.res.Resources; import android.os.PersistableBundle; import android.text.format.Formatter; import android.util.ArrayMap; @@ -48,6 +51,8 @@ import java.util.List; import java.util.Set; import java.util.function.Predicate; +import sun.misc.Resource; + /** * Package information used by {@link ShortcutService}. * User information used by {@link ShortcutService}. @@ -72,15 +77,19 @@ class ShortcutPackage extends ShortcutPackageItem { private static final String ATTR_ACTIVITY = "activity"; private static final String ATTR_TITLE = "title"; private static final String ATTR_TITLE_RES_ID = "titleid"; + private static final String ATTR_TITLE_RES_NAME = "titlename"; private static final String ATTR_TEXT = "text"; private static final String ATTR_TEXT_RES_ID = "textid"; + private static final String ATTR_TEXT_RES_NAME = "textname"; private static final String ATTR_DISABLED_MESSAGE = "dmessage"; private static final String ATTR_DISABLED_MESSAGE_RES_ID = "dmessageid"; + private static final String ATTR_DISABLED_MESSAGE_RES_NAME = "dmessagename"; private static final String ATTR_INTENT = "intent"; private static final String ATTR_RANK = "rank"; private static final String ATTR_TIMESTAMP = "timestamp"; private static final String ATTR_FLAGS = "flags"; - private static final String ATTR_ICON_RES = "icon-res"; + private static final String ATTR_ICON_RES_ID = "icon-res"; + private static final String ATTR_ICON_RES_NAME = "icon-resname"; private static final String ATTR_BITMAP_PATH = "bitmap-path"; private static final String NAME_CATEGORIES = "categories"; @@ -153,6 +162,12 @@ class ShortcutPackage extends ShortcutPackageItem { } } + @Nullable + public Resources getPackageResources() { + return mShortcutUser.mService.injectGetResourcesForApplicationAsUser( + getPackageName(), getPackageUserId()); + } + @Override protected void onRestoreBlocked() { // Can't restore due to version/signature mismatch. Remove all shortcuts. @@ -209,8 +224,13 @@ class ShortcutPackage extends ShortcutPackageItem { } private void addShortcutInner(@NonNull ShortcutInfo newShortcut) { + final ShortcutService s = mShortcutUser.mService; + deleteShortcutInner(newShortcut.getId()); - mShortcutUser.mService.saveIconAndFixUpShortcut(getPackageUserId(), newShortcut); + + // Extract Icon and update the icon res ID and the bitmap path. + s.saveIconAndFixUpShortcut(getPackageUserId(), newShortcut); + s.fixUpShortcutResourceNamesAndValues(newShortcut); mShortcuts.put(newShortcut.getId(), newShortcut); } @@ -248,7 +268,7 @@ class ShortcutPackage extends ShortcutPackageItem { // TODO Check max dynamic count. // mShortcutUser.mService.enforceMaxDynamicShortcuts(newDynamicCount); - // Okay, make it dynamic and add. + // If it was originally pinned, the new one should be pinned too. if (wasPinned) { newShortcut.addFlags(ShortcutInfo.FLAG_PINNED); } @@ -318,6 +338,8 @@ class ShortcutPackage extends ShortcutPackageItem { disabled.setDisabledMessage(disabledMessage); } else if (disabledMessageResId != 0) { disabled.setDisabledMessageResId(disabledMessageResId); + + mShortcutUser.mService.fixUpShortcutResourceNamesAndValues(disabled); } } } @@ -622,10 +644,25 @@ class ShortcutPackage extends ShortcutPackageItem { // For existing shortcuts, update timestamps if they have any resources. if (!isNewApp) { + Resources publisherRes = null; + for (int i = mShortcuts.size() - 1; i >= 0; i--) { final ShortcutInfo si = mShortcuts.valueAt(i); if (si.hasAnyResources()) { + if (!si.isOriginallyFromManifest()) { + if (publisherRes == null) { + publisherRes = getPackageResources(); + if (publisherRes == null) { + break; // Resources couldn't be loaded. + } + } + + // If this shortcut is not from a manifest, then update all resource IDs + // from resource names. (We don't allow resource strings for + // non-manifest at the moment, but icons can still be resources.) + si.lookupAndFillInResourceIds(publisherRes); + } changed = true; si.setTimestamp(s.injectCurrentTimeMillis()); } @@ -869,7 +906,9 @@ class ShortcutPackage extends ShortcutPackageItem { final ComponentName newActivity = newShortcut.getActivity(); if (newActivity == null) { if (operation != ShortcutService.OPERATION_UPDATE) { - service.wtf("null Activity found for non-update"); + // This method may be called before validating shortcuts, so this may happen, + // and is a caller side error. + throw new NullPointerException("activity must be provided"); } continue; // Activity can be null for update. } @@ -907,6 +946,36 @@ class ShortcutPackage extends ShortcutPackageItem { } } + /** + * For all the text fields, refresh the string values if they're from resources. + */ + public void resolveResourceStrings() { + final ShortcutService s = mShortcutUser.mService; + boolean changed = false; + + Resources publisherRes = null; + for (int i = mShortcuts.size() - 1; i >= 0; i--) { + final ShortcutInfo si = mShortcuts.valueAt(i); + + if (si.hasStringResources()) { + changed = true; + + if (publisherRes == null) { + publisherRes = getPackageResources(); + if (publisherRes == null) { + break; // Resources couldn't be loaded. + } + } + + si.resolveResourceStrings(publisherRes); + si.setTimestamp(s.injectCurrentTimeMillis()); + } + } + if (changed) { + s.scheduleSaveUser(getPackageUserId()); + } + } + public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { pw.println(); @@ -1008,11 +1077,15 @@ class ShortcutPackage extends ShortcutPackageItem { // 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()); + ShortcutService.writeAttr(out, ATTR_TITLE_RES_NAME, si.getTitleResName()); ShortcutService.writeAttr(out, ATTR_TEXT, si.getText()); ShortcutService.writeAttr(out, ATTR_TEXT_RES_ID, si.getTextResId()); + ShortcutService.writeAttr(out, ATTR_TEXT_RES_NAME, si.getTextResName()); ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE, si.getDisabledMessage()); ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_ID, si.getDisabledMessageResourceId()); + ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_NAME, + si.getDisabledMessageResName()); ShortcutService.writeAttr(out, ATTR_INTENT, si.getIntentNoExtras()); ShortcutService.writeAttr(out, ATTR_RANK, si.getRank()); ShortcutService.writeAttr(out, ATTR_TIMESTAMP, @@ -1025,7 +1098,8 @@ class ShortcutPackage extends ShortcutPackageItem { | ShortcutInfo.FLAG_DYNAMIC)); } else { ShortcutService.writeAttr(out, ATTR_FLAGS, si.getFlags()); - ShortcutService.writeAttr(out, ATTR_ICON_RES, si.getIconResourceId()); + ShortcutService.writeAttr(out, ATTR_ICON_RES_ID, si.getIconResourceId()); + ShortcutService.writeAttr(out, ATTR_ICON_RES_NAME, si.getIconResName()); ShortcutService.writeAttr(out, ATTR_BITMAP_PATH, si.getBitmapPath()); } @@ -1096,17 +1170,21 @@ class ShortcutPackage extends ShortcutPackageItem { // Icon icon; String title; int titleResId; + String titleResName; String text; int textResId; + String textResName; String disabledMessage; int disabledMessageResId; + String disabledMessageResName; Intent intent; PersistableBundle intentPersistableExtras = null; int rank; PersistableBundle extras = null; long lastChangedTimestamp; int flags; - int iconRes; + int iconResId; + String iconResName; String bitmapPath; ArraySet<String> categories = null; @@ -1115,16 +1193,21 @@ class ShortcutPackage extends ShortcutPackageItem { ATTR_ACTIVITY); title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE); titleResId = ShortcutService.parseIntAttribute(parser, ATTR_TITLE_RES_ID); + titleResName = ShortcutService.parseStringAttribute(parser, ATTR_TITLE_RES_NAME); text = ShortcutService.parseStringAttribute(parser, ATTR_TEXT); textResId = ShortcutService.parseIntAttribute(parser, ATTR_TEXT_RES_ID); + textResName = ShortcutService.parseStringAttribute(parser, ATTR_TEXT_RES_NAME); disabledMessage = ShortcutService.parseStringAttribute(parser, ATTR_DISABLED_MESSAGE); disabledMessageResId = ShortcutService.parseIntAttribute(parser, ATTR_DISABLED_MESSAGE_RES_ID); + disabledMessageResName = ShortcutService.parseStringAttribute(parser, + ATTR_DISABLED_MESSAGE_RES_NAME); intent = ShortcutService.parseIntentAttribute(parser, ATTR_INTENT); rank = (int) ShortcutService.parseLongAttribute(parser, ATTR_RANK); lastChangedTimestamp = ShortcutService.parseLongAttribute(parser, ATTR_TIMESTAMP); flags = (int) ShortcutService.parseLongAttribute(parser, ATTR_FLAGS); - iconRes = (int) ShortcutService.parseLongAttribute(parser, ATTR_ICON_RES); + iconResId = (int) ShortcutService.parseLongAttribute(parser, ATTR_ICON_RES_ID); + iconResName = ShortcutService.parseStringAttribute(parser, ATTR_ICON_RES_NAME); bitmapPath = ShortcutService.parseStringAttribute(parser, ATTR_BITMAP_PATH); final int outerDepth = parser.getDepth(); @@ -1167,10 +1250,11 @@ class ShortcutPackage extends ShortcutPackageItem { return new ShortcutInfo( userId, id, packageName, activityComponent, /* icon =*/ null, - title, titleResId, text, textResId, disabledMessage, disabledMessageResId, + title, titleResId, titleResName, text, textResId, textResName, + disabledMessage, disabledMessageResId, disabledMessageResName, categories, intent, intentPersistableExtras, rank, extras, lastChangedTimestamp, flags, - iconRes, bitmapPath); + iconResId, iconResName, bitmapPath); } @VisibleForTesting diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java index 3eda13ee79b9..470d4afe6444 100644 --- a/services/core/java/com/android/server/pm/ShortcutParser.java +++ b/services/core/java/com/android/server/pm/ShortcutParser.java @@ -221,6 +221,8 @@ public class ShortcutParser { | ShortcutInfo.FLAG_IMMUTABLE | ((iconResId != 0) ? ShortcutInfo.FLAG_HAS_ICON_RES : 0); + // Note we don't need to set resource names here yet. They'll be set when they're about + // to be published. return new ShortcutInfo( userId, id, @@ -229,10 +231,13 @@ public class ShortcutParser { null, // icon null, // title string titleResId, + null, // title res name null, // text string textResId, + null, // text res name null, // disabled message string disabledMessageResId, + null, // disabled message res name categories, intent, null, // intent extras @@ -241,6 +246,7 @@ public class ShortcutParser { service.injectCurrentTimeMillis(), flags, iconResId, + null, // icon res name 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 356515442386..57694028d86d 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -42,6 +42,7 @@ import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutServiceInternal; import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener; +import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; @@ -120,6 +121,9 @@ import java.util.function.Predicate; /** * TODO: + * - Deal with the async nature of PACKAGE_ADD. Basically when a publisher does anything after + * it's upgraded, the manager should make sure the upgrade process has been executed. + * * - HandleUnlockUser needs to be async. Wait on it in onCleanupUser. * * - Implement reportShortcutUsed(). @@ -319,8 +323,10 @@ public class ShortcutService extends IShortcutService.Stub { int GET_ACTIVITIES_WITH_METADATA = 6; int GET_INSTALLED_PACKAGES = 7; int CHECK_PACKAGE_CHANGES = 8; + int GET_APPLICATION_RESOURCES = 9; + int RESOURCE_NAME_LOOKUP = 10; - int COUNT = CHECK_PACKAGE_CHANGES + 1; + int COUNT = RESOURCE_NAME_LOOKUP + 1; } final Object mStatLock = new Object(); @@ -1266,6 +1272,24 @@ public class ShortcutService extends IShortcutService.Stub { return scaledBitmap; } + /** + * For a shortcut, update all resource names from resource IDs, and also update all + * resource-based strings. + */ + void fixUpShortcutResourceNamesAndValues(ShortcutInfo si) { + final Resources publisherRes = injectGetResourcesForApplicationAsUser( + si.getPackage(), si.getUserId()); + if (publisherRes != null) { + final long start = injectElapsedRealtime(); + try { + si.lookupAndFillInResourceNames(publisherRes); + } finally { + logDurationStat(Stats.RESOURCE_NAME_LOOKUP, start); + } + si.resolveResourceStrings(publisherRes); + } + } + // === Caller validation === private boolean isCallerSystem() { @@ -1328,7 +1352,8 @@ public class ShortcutService extends IShortcutService.Stub { throw new SecurityException("Calling package name mismatch"); } - void postToHandler(Runnable r) { + // Overridden in unit tests to execute r synchronously. + void injectPostToHandler(Runnable r) { mHandler.post(r); } @@ -1371,7 +1396,7 @@ public class ShortcutService extends IShortcutService.Stub { } finally { injectRestoreCallingIdentity(token); } - postToHandler(() -> { + injectPostToHandler(() -> { final ArrayList<ShortcutChangeListener> copy; synchronized (mLock) { copy = new ArrayList<>(mListeners); @@ -1538,12 +1563,18 @@ public class ShortcutService extends IShortcutService.Stub { // TODO When activity is changing, check the dynamic count. } - // Note copyNonNullFieldsFrom() does the "udpatable with?" check too. + // Note copyNonNullFieldsFrom() does the "updatable with?" check too. target.copyNonNullFieldsFrom(source); if (replacingIcon) { saveIconAndFixUpShortcut(userId, target); } + + // When we're updating any resource related fields, re-extract the res names and + // the values. + if (replacingIcon || source.hasStringResources()) { + fixUpShortcutResourceNamesAndValues(target); + } } } } @@ -1981,21 +2012,6 @@ public class ShortcutService extends IShortcutService.Stub { }); } } - // Resolve all strings if needed. - if (!cloneKeyFieldOnly) { - final long token = injectClearCallingIdentity(); - try { - for (int i = ret.size() - 1; i >= 0; i--) { - try { - ret.get(i).resolveStringsRequiringCrossUser(mContext); - } catch (NameNotFoundException e) { - continue; - } - } - } finally { - injectRestoreCallingIdentity(token); - } - } return ret; } @@ -2218,11 +2234,25 @@ public class ShortcutService extends IShortcutService.Stub { if (DEBUG) { Slog.d(TAG, "onSystemLocaleChangedNoLock: " + mLocaleChangeSequenceNumber.get()); } - postToHandler(() -> scheduleSaveBaseState()); + injectPostToHandler(() -> handleLocaleChanged()); } } } + void handleLocaleChanged() { + if (DEBUG) { + Slog.d(TAG, "handleLocaleChanged"); + } + scheduleSaveBaseState(); + + final long token = injectClearCallingIdentity(); + try { + forEachLoadedUserLocked(u -> u.forAllPackages(p -> p.resolveResourceStrings())); + } finally { + injectRestoreCallingIdentity(token); + } + } + /** * Package event callbacks. */ @@ -2482,10 +2512,26 @@ public class ShortcutService extends IShortcutService.Stub { @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); } + @Nullable + Resources injectGetResourcesForApplicationAsUser(String packageName, int userId) { + final long start = injectElapsedRealtime(); + final long token = injectClearCallingIdentity(); + try { + return mContext.getPackageManager().getResourcesForApplicationAsUser( + packageName, userId); + } catch (NameNotFoundException e) { + Slog.e(TAG, "Resources for package " + packageName + " not found"); + return null; + } finally { + injectRestoreCallingIdentity(token); + + logDurationStat(Stats.GET_APPLICATION_RESOURCES, start); + } + } + // === Backup & restore === boolean shouldBackupApp(String packageName, int userId) { @@ -2628,6 +2674,8 @@ public class ShortcutService extends IShortcutService.Stub { dumpStatLS(pw, p, Stats.GET_ACTIVITIES_WITH_METADATA, "getActivities+metadata"); dumpStatLS(pw, p, Stats.GET_INSTALLED_PACKAGES, "getInstalledPackages"); dumpStatLS(pw, p, Stats.CHECK_PACKAGE_CHANGES, "checkPackageChanges"); + dumpStatLS(pw, p, Stats.GET_APPLICATION_RESOURCES, "getApplicationResources"); + dumpStatLS(pw, p, Stats.RESOURCE_NAME_LOOKUP, "resourceNameLookup"); } for (int i = 0; i < mUsers.size(); i++) { diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index d33047b68757..8ce7304b52ff 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -22,6 +22,8 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; @@ -92,6 +94,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.function.BiPredicate; @@ -317,7 +320,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } @Override - void postToHandler(Runnable r) { + void injectPostToHandler(Runnable r) { final long token = mContext.injectClearCallingIdentity(); r.run(); mContext.injectRestoreCallingIdentity(token); @@ -441,6 +444,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { protected boolean mInjectedIsLowRamDevice; + protected Locale mInjectedLocale = Locale.ENGLISH; + protected int mInjectedCallingUid; protected String mInjectedClientPackage; @@ -597,6 +602,9 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { // User 0 is always running. when(mMockUserManager.isUserRunning(eq(USER_0))).thenAnswer(new AnswerIsUserRunning(true)); + setUpAppResources(); + + // Start the service. initService(); setCaller(CALLING_PACKAGE_1); @@ -624,6 +632,53 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } } + protected void setUpAppResources() throws Exception { + setUpAppResources(/* offset = */ 0); + } + + protected void setUpAppResources(int ressIdOffset) throws Exception { + // ressIdOffset is used to adjust resource IDs to emulate the case where an updated app + // has resource IDs changed. + + doAnswer(pmInvocation -> { + assertEquals(Process.SYSTEM_UID, mInjectedCallingUid); + + final String packageName = (String) pmInvocation.getArguments()[0]; + final int userId = (Integer) pmInvocation.getArguments()[1]; + + final Resources res = mock(Resources.class); + + doAnswer(resInvocation -> { + final int argResId = (Integer) resInvocation.getArguments()[0]; + + return "string-" + packageName + "-user:" + userId + "-res:" + argResId + + "/" + mInjectedLocale; + }).when(res).getString(anyInt()); + + doAnswer(resInvocation -> { + final int resId = (Integer) resInvocation.getArguments()[0]; + + // Always use the "string" resource type. The type doesn't matter during the test. + return packageName + ":string/r" + resId; + }).when(res).getResourceName(anyInt()); + + doAnswer(resInvocation -> { + final String argResName = (String) resInvocation.getArguments()[0]; + final String argType = (String) resInvocation.getArguments()[1]; + final String argPackageName = (String) resInvocation.getArguments()[2]; + + // See the above code. getResourceName() will just use "r" + res ID as the entry + // name. + String entryName = argResName; + if (entryName.contains("/")) { + entryName = ShortcutInfo.getResourceEntryName(entryName); + } + return Integer.parseInt(entryName.substring(1)) + ressIdOffset; + }).when(res).getIdentifier(anyString(), anyString(), anyString()); + return res; + }).when(mMockPackageManager).getResourcesForApplicationAsUser(anyString(), anyInt()); + } + protected static UserInfo withProfileGroupId(UserInfo in, int groupId) { in.profileGroupId = groupId; return in; @@ -667,8 +722,16 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { mLauncherApps = null; mLauncherAppsMap.clear(); - // Load the setting file. + // Send boot sequence events. mService.onBootPhase(SystemService.PHASE_LOCK_SETTINGS_READY); + + // Make sure a call to onSystemLocaleChangedNoLock() before PHASE_BOOT_COMPLETED will be + // ignored. + final long origSequenceNumber = mService.getLocaleChangeSequenceNumber(); + mInternal.onSystemLocaleChangedNoLock(); + assertEquals(origSequenceNumber, mService.getLocaleChangeSequenceNumber()); + + mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); } protected void shutdownServices() { @@ -891,6 +954,18 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { setCaller(previousPackage, previousUserId); } + protected void runWithSystemUid(Runnable r) { + final int origUid = mInjectedCallingUid; + mInjectedCallingUid = Process.SYSTEM_UID; + r.run(); + mInjectedCallingUid = origUid; + } + + protected void lookupAndFillInResourceNames(ShortcutInfo si) { + runWithSystemUid(() -> si.lookupAndFillInResourceNames( + mService.injectGetResourcesForApplicationAsUser(si.getPackage(), si.getUserId()))); + } + protected int getCallingUserId() { return UserHandle.getUserId(mInjectedCallingUid); } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index c11be7a3a1c2..fe2d1eca1cf1 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -95,6 +95,7 @@ import org.mockito.ArgumentCaptor; import java.io.File; import java.io.IOException; import java.util.List; +import java.util.Locale; /** * Tests for ShortcutService and ShortcutManager. @@ -922,6 +923,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { ShortcutInfo s = getCallerShortcut("s2"); assertTrue(s.hasIconResource()); assertEquals(R.drawable.black_32x32, s.getIconResourceId()); + assertEquals("string/r" + R.drawable.black_32x32, s.getIconResName()); assertEquals("Title-s2", s.getTitle()); s = getCallerShortcut("s4"); @@ -1091,21 +1093,6 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { } public void testGetShortcuts_resolveStrings() throws Exception { - doAnswer(pmInvocation -> { - assertEquals(Process.SYSTEM_UID, mInjectedCallingUid); - - final String packageName = (String) pmInvocation.getArguments()[0]; - final int userId = (Integer) pmInvocation.getArguments()[1]; - - final Resources res = mock(Resources.class); - doAnswer(resInvocation -> { - final int resId = (Integer) resInvocation.getArguments()[0]; - - return "string-" + packageName + "-user:" + userId + "-res:" + resId; - }).when(res).getString(anyInt()); - return res; - }).when(mMockPackageManager).getResourcesForApplicationAsUser(anyString(), anyInt()); - runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { ShortcutInfo si = new ShortcutInfo.Builder(mClientContext) .setId("id") @@ -1137,18 +1124,18 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { List<ShortcutInfo> ret = assertShortcutIds( assertAllStringsResolved(mLauncherApps.getShortcuts(q, HANDLE_USER_0)), "id"); - assertEquals("string-com.android.test.1-user:0-res:10", ret.get(0).getTitle()); - assertEquals("string-com.android.test.1-user:0-res:11", ret.get(0).getText()); - assertEquals("string-com.android.test.1-user:0-res:12", + assertEquals("string-com.android.test.1-user:0-res:10/en", ret.get(0).getTitle()); + assertEquals("string-com.android.test.1-user:0-res:11/en", ret.get(0).getText()); + assertEquals("string-com.android.test.1-user:0-res:12/en", ret.get(0).getDisabledMessage()); // USER P0 ret = assertShortcutIds( assertAllStringsResolved(mLauncherApps.getShortcuts(q, HANDLE_USER_P0)), "id"); - assertEquals("string-com.android.test.1-user:20-res:10", ret.get(0).getTitle()); - assertEquals("string-com.android.test.1-user:20-res:11", ret.get(0).getText()); - assertEquals("string-com.android.test.1-user:20-res:12", + assertEquals("string-com.android.test.1-user:20-res:10/en", ret.get(0).getTitle()); + assertEquals("string-com.android.test.1-user:20-res:11/en", ret.get(0).getText()); + assertEquals("string-com.android.test.1-user:20-res:12/en", ret.get(0).getDisabledMessage()); }); } @@ -3602,6 +3589,84 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { findShortcut(shortcuts.getValue(), "s1").getLastChangedTimestamp()); } + /** + * Test the case where an updated app has resource IDs changed. + */ + public void testHandlePackageUpdate_resIdChanged() throws Exception { + final Icon icon1 = Icon.createWithResource(getTestContext(), /* res ID */ 1000); + final Icon icon2 = Icon.createWithResource(getTestContext(), /* res ID */ 1001); + + // Set up shortcuts. + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + // Note resource strings are not officially supported (they're hidden), but + // should work. + + final ShortcutInfo s1 = new ShortcutInfo.Builder(mClientContext) + .setId("s1") + .setActivity(makeComponent(ShortcutActivity.class)) + .setIntent(new Intent(Intent.ACTION_VIEW)) + .setIcon(icon1) + .setTitleResId(10000) + .setTextResId(10001) + .setDisabledMessageResId(10002) + .build(); + + final ShortcutInfo s2 = new ShortcutInfo.Builder(mClientContext) + .setId("s2") + .setActivity(makeComponent(ShortcutActivity.class)) + .setIntent(new Intent(Intent.ACTION_VIEW)) + .setIcon(icon2) + .setTitleResId(20000) + .build(); + + assertTrue(mManager.setDynamicShortcuts(list(s1, s2))); + }); + + // Verify. + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + final ShortcutInfo s1 = getCallerShortcut("s1"); + final ShortcutInfo s2 = getCallerShortcut("s2"); + + assertEquals(1000, s1.getIconResourceId()); + assertEquals(10000, s1.getTitleResId()); + assertEquals(10001, s1.getTextResId()); + assertEquals(10002, s1.getDisabledMessageResourceId()); + + assertEquals(1001, s2.getIconResourceId()); + assertEquals(20000, s2.getTitleResId()); + assertEquals(0, s2.getTextResId()); + assertEquals(0, s2.getDisabledMessageResourceId()); + }); + + mService.saveDirtyInfo(); + initService(); + + // Set up the mock resources again, with an "adjustment". + // When the package is updated, the service will fetch the updated res-IDs with res-names, + // and the new IDs will have this offset. + setUpAppResources(10); + + // Update the package. + updatePackageVersion(CALLING_PACKAGE_1, 1); + mService.mPackageMonitor.onReceive(getTestContext(), + genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + final ShortcutInfo s1 = getCallerShortcut("s1"); + final ShortcutInfo s2 = getCallerShortcut("s2"); + + assertEquals(1010, s1.getIconResourceId()); + assertEquals(10010, s1.getTitleResId()); + assertEquals(10011, s1.getTextResId()); + assertEquals(10012, s1.getDisabledMessageResourceId()); + + assertEquals(1011, s2.getIconResourceId()); + assertEquals(20010, s2.getTitleResId()); + assertEquals(0, s2.getTextResId()); + assertEquals(0, s2.getDisabledMessageResourceId()); + }); + } + protected void prepareForBackupTest() { prepareCrossProfileDataSet(); @@ -4785,27 +4850,35 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals(R.drawable.icon1, si.getIconResourceId()); assertEquals(new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), si.getActivity()); + assertEquals(R.string.shortcut_title1, si.getTitleResId()); + assertEquals("r" + R.string.shortcut_title1, si.getTitleResName()); assertEquals(R.string.shortcut_text1, si.getTextResId()); + assertEquals("r" + R.string.shortcut_text1, si.getTextResName()); assertEquals(R.string.shortcut_disabled_message1, si.getDisabledMessageResourceId()); + assertEquals("r" + R.string.shortcut_disabled_message1, si.getDisabledMessageResName()); + assertEquals(set("android.shortcut.conversation", "android.shortcut.media"), si.getCategories()); assertEquals("action1", si.getIntent().getAction()); assertEquals(Uri.parse("http://a.b.c/1"), si.getIntent().getData()); - assertEquals(0, si.getRank()); // check another si = getCallerShortcut("ms2"); assertEquals("ms2", si.getId()); assertEquals(R.drawable.icon2, si.getIconResourceId()); + assertEquals(R.string.shortcut_title2, si.getTitleResId()); + assertEquals("r" + R.string.shortcut_title2, si.getTitleResName()); assertEquals(R.string.shortcut_text2, si.getTextResId()); + assertEquals("r" + R.string.shortcut_text2, si.getTextResName()); assertEquals(R.string.shortcut_disabled_message2, si.getDisabledMessageResourceId()); + assertEquals("r" + R.string.shortcut_disabled_message2, si.getDisabledMessageResName()); + assertEquals(set("android.shortcut.conversation"), si.getCategories()); assertEquals("action2", si.getIntent().getAction()); assertEquals(null, si.getIntent().getData()); - assertEquals(1, si.getRank()); // check another si = getCallerShortcut("ms3"); @@ -4813,12 +4886,113 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals("ms3", si.getId()); assertEquals(0, si.getIconResourceId()); assertEquals(R.string.shortcut_title1, si.getTitleResId()); + assertEquals("r" + R.string.shortcut_title1, si.getTitleResName()); + assertEquals(0, si.getTextResId()); + assertEquals(null, si.getTextResName()); assertEquals(0, si.getDisabledMessageResourceId()); + assertEquals(null, si.getDisabledMessageResName()); + assertEquals(null, si.getCategories()); assertEquals("android.intent.action.VIEW", si.getIntent().getAction()); assertEquals(null, si.getIntent().getData()); - assertEquals(2, si.getRank()); + }); + } + + public void testManifestShortcuts_localeChange() { + 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_2); + updatePackageVersion(CALLING_PACKAGE_1, 1); + mService.mPackageMonitor.onReceive(getTestContext(), + genPackageAddIntent(CALLING_PACKAGE_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + mManager.setDynamicShortcuts(list(makeShortcutWithTitle("s1", "title"))); + + assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled( + mManager.getManifestShortcuts()))), + "ms1", "ms2"); + + // check first shortcut. + ShortcutInfo si = getCallerShortcut("ms1"); + + assertEquals("ms1", si.getId()); + assertEquals("string-com.android.test.1-user:0-res:" + R.string.shortcut_title1 + "/en", + si.getTitle()); + assertEquals("string-com.android.test.1-user:0-res:" + R.string.shortcut_text1 + "/en", + si.getText()); + assertEquals("string-com.android.test.1-user:0-res:" + + R.string.shortcut_disabled_message1 + "/en", + si.getDisabledMessage()); + assertEquals(START_TIME, si.getLastChangedTimestamp()); + + // check another + si = getCallerShortcut("ms2"); + + assertEquals("ms2", si.getId()); + assertEquals("string-com.android.test.1-user:0-res:" + R.string.shortcut_title2 + "/en", + si.getTitle()); + assertEquals("string-com.android.test.1-user:0-res:" + R.string.shortcut_text2 + "/en", + si.getText()); + assertEquals("string-com.android.test.1-user:0-res:" + + R.string.shortcut_disabled_message2 + "/en", + si.getDisabledMessage()); + assertEquals(START_TIME, si.getLastChangedTimestamp()); + + // Check the dynamic one. + si = getCallerShortcut("s1"); + + assertEquals("s1", si.getId()); + assertEquals("title", si.getTitle()); + assertEquals(null, si.getText()); + assertEquals(null, si.getDisabledMessage()); + assertEquals(START_TIME, si.getLastChangedTimestamp()); + }); + + mInjectedCurrentTimeMillis++; + + mInjectedLocale = Locale.JAPANESE; + mInternal.onSystemLocaleChangedNoLock(); + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + // check first shortcut. + ShortcutInfo si = getCallerShortcut("ms1"); + + assertEquals("ms1", si.getId()); + assertEquals("string-com.android.test.1-user:0-res:" + R.string.shortcut_title1 + "/ja", + si.getTitle()); + assertEquals("string-com.android.test.1-user:0-res:" + R.string.shortcut_text1 + "/ja", + si.getText()); + assertEquals("string-com.android.test.1-user:0-res:" + + R.string.shortcut_disabled_message1 + "/ja", + si.getDisabledMessage()); + assertEquals(START_TIME + 1, si.getLastChangedTimestamp()); + + // check another + si = getCallerShortcut("ms2"); + + assertEquals("ms2", si.getId()); + assertEquals("string-com.android.test.1-user:0-res:" + R.string.shortcut_title2 + "/ja", + si.getTitle()); + assertEquals("string-com.android.test.1-user:0-res:" + R.string.shortcut_text2 + "/ja", + si.getText()); + assertEquals("string-com.android.test.1-user:0-res:" + + R.string.shortcut_disabled_message2 + "/ja", + si.getDisabledMessage()); + assertEquals(START_TIME + 1, si.getLastChangedTimestamp()); + + // Check the dynamic one. (locale change shouldn't affect.) + si = getCallerShortcut("s1"); + + assertEquals("s1", si.getId()); + assertEquals("title", si.getTitle()); + assertEquals(null, si.getText()); + assertEquals(null, si.getDisabledMessage()); + assertEquals(START_TIME, si.getLastChangedTimestamp()); // Not changed. }); } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index 1702ca4b2d53..399fddfcfb87 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -32,6 +32,7 @@ import android.app.ActivityManager; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ShortcutInfo; +import android.content.res.Resources; import android.graphics.BitmapFactory; import android.graphics.drawable.Icon; import android.os.PersistableBundle; @@ -40,7 +41,6 @@ import android.test.MoreAsserts; import android.test.suitebuilder.annotation.SmallTest; import com.android.frameworks.servicestests.R; -import com.android.server.SystemService; /** * Tests for ShortcutService and ShortcutManager. @@ -138,6 +138,13 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags()); assertEquals("abc", si.getBitmapPath()); assertEquals(456, si.getIconResourceId()); + + assertEquals(0, si.getTitleResId()); + assertEquals(null, si.getTitleResName()); + assertEquals(0, si.getTextResId()); + assertEquals(null, si.getTextResName()); + assertEquals(0, si.getDisabledMessageResourceId()); + assertEquals(null, si.getDisabledMessageResName()); } public void testShortcutInfoParcel_resId() { @@ -163,6 +170,8 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { si.setBitmapPath("abc"); si.setIconResourceId(456); + lookupAndFillInResourceNames(si); + si = parceled(si); assertEquals(getTestContext().getPackageName(), si.getPackage()); @@ -170,8 +179,11 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(new ComponentName("a", "b"), si.getActivity()); assertEquals(123, si.getIcon().getResId()); assertEquals(10, si.getTitleResId()); + assertEquals("r10", si.getTitleResName()); assertEquals(11, si.getTextResId()); + assertEquals("r11", si.getTextResName()); assertEquals(12, si.getDisabledMessageResourceId()); + assertEquals("r12", si.getDisabledMessageResName()); assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories()); assertEquals("action", si.getIntent().getAction()); assertEquals("val", si.getIntent().getStringExtra("key")); @@ -181,6 +193,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags()); assertEquals("abc", si.getBitmapPath()); assertEquals(456, si.getIconResourceId()); + assertEquals("string/r456", si.getIconResName()); } public void testShortcutInfoClone() { @@ -204,6 +217,8 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { sorig.setBitmapPath("abc"); sorig.setIconResourceId(456); + lookupAndFillInResourceNames(sorig); + ShortcutInfo si = sorig.clone(/* clone flags*/ 0); assertEquals(USER_11, si.getUserId()); @@ -224,6 +239,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags()); assertEquals("abc", si.getBitmapPath()); assertEquals(456, si.getIconResourceId()); + assertEquals("string/r456", si.getIconResName()); si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR); @@ -244,6 +260,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(null, si.getBitmapPath()); assertEquals(456, si.getIconResourceId()); + assertEquals(null, si.getIconResName()); si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER); @@ -263,6 +280,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(null, si.getBitmapPath()); assertEquals(456, si.getIconResourceId()); + assertEquals(null, si.getIconResName()); si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); @@ -282,6 +300,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(null, si.getBitmapPath()); assertEquals(456, si.getIconResourceId()); + assertEquals(null, si.getIconResName()); } public void testShortcutInfoClone_resId() { @@ -305,6 +324,8 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { sorig.setBitmapPath("abc"); sorig.setIconResourceId(456); + lookupAndFillInResourceNames(sorig); + ShortcutInfo si = sorig.clone(/* clone flags*/ 0); assertEquals(USER_11, si.getUserId()); @@ -314,8 +335,11 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(new ComponentName("a", "b"), si.getActivity()); assertEquals(123, si.getIcon().getResId()); assertEquals(10, si.getTitleResId()); + assertEquals("r10", si.getTitleResName()); assertEquals(11, si.getTextResId()); + assertEquals("r11", si.getTextResName()); assertEquals(12, si.getDisabledMessageResourceId()); + assertEquals("r12", si.getDisabledMessageResName()); assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories()); assertEquals("action", si.getIntent().getAction()); assertEquals("val", si.getIntent().getStringExtra("key")); @@ -325,6 +349,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags()); assertEquals("abc", si.getBitmapPath()); assertEquals(456, si.getIconResourceId()); + assertEquals("string/r456", si.getIconResName()); si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR); @@ -333,8 +358,11 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(new ComponentName("a", "b"), si.getActivity()); assertEquals(null, si.getIcon()); assertEquals(10, si.getTitleResId()); + assertEquals(null, si.getTitleResName()); assertEquals(11, si.getTextResId()); + assertEquals(null, si.getTextResName()); assertEquals(12, si.getDisabledMessageResourceId()); + assertEquals(null, si.getDisabledMessageResName()); assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories()); assertEquals("action", si.getIntent().getAction()); assertEquals("val", si.getIntent().getStringExtra("key")); @@ -345,6 +373,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(null, si.getBitmapPath()); assertEquals(456, si.getIconResourceId()); + assertEquals(null, si.getIconResName()); si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER); @@ -353,8 +382,11 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(new ComponentName("a", "b"), si.getActivity()); assertEquals(null, si.getIcon()); assertEquals(10, si.getTitleResId()); + assertEquals(null, si.getTitleResName()); assertEquals(11, si.getTextResId()); + assertEquals(null, si.getTextResName()); assertEquals(12, si.getDisabledMessageResourceId()); + assertEquals(null, si.getDisabledMessageResName()); assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories()); assertEquals(null, si.getIntent()); assertEquals(123, si.getRank()); @@ -364,6 +396,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(null, si.getBitmapPath()); assertEquals(456, si.getIconResourceId()); + assertEquals(null, si.getIconResName()); si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); @@ -372,8 +405,11 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(null, si.getActivity()); assertEquals(null, si.getIcon()); assertEquals(0, si.getTitleResId()); + assertEquals(null, si.getTitleResName()); assertEquals(0, si.getTextResId()); + assertEquals(null, si.getTextResName()); assertEquals(0, si.getDisabledMessageResourceId()); + assertEquals(null, si.getDisabledMessageResName()); assertEquals(null, si.getCategories()); assertEquals(null, si.getIntent()); assertEquals(0, si.getRank()); @@ -383,6 +419,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(null, si.getBitmapPath()); assertEquals(456, si.getIconResourceId()); + assertEquals(null, si.getIconResName()); } public void testShortcutInfoClone_minimum() { @@ -445,6 +482,8 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { sorig.setBitmapPath("abc"); sorig.setIconResourceId(456); + lookupAndFillInResourceNames(sorig); + ShortcutInfo si; si = sorig.clone(/* flags=*/ 0); @@ -458,6 +497,9 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { .setIcon(Icon.createWithResource(mClientContext, 456)).build()); assertEquals("text", si.getText()); assertEquals(456, si.getIcon().getResId()); + assertEquals(0, si.getIconResourceId()); + assertEquals(null, si.getIconResName()); + assertEquals(null, si.getBitmapPath()); si = sorig.clone(/* flags=*/ 0); si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") @@ -586,6 +628,9 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { .setIcon(Icon.createWithResource(mClientContext, 456)).build()); assertEquals(11, si.getTextResId()); assertEquals(456, si.getIcon().getResId()); + assertEquals(0, si.getIconResourceId()); + assertEquals(null, si.getIconResName()); + assertEquals(null, si.getBitmapPath()); si = sorig.clone(/* flags=*/ 0); si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") @@ -593,6 +638,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(11, si.getTextResId()); assertEquals("xyz", si.getTitle()); assertEquals(0, si.getTitleResId()); + assertEquals(null, si.getTitleResName()); si = sorig.clone(/* flags=*/ 0); si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") @@ -600,6 +646,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(11, si.getTextResId()); assertEquals(null, si.getTitle()); assertEquals(123, si.getTitleResId()); + assertEquals(null, si.getTitleResName()); si = sorig.clone(/* flags=*/ 0); si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") @@ -607,6 +654,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(123, si.getRank()); assertEquals("xxx", si.getText()); assertEquals(0, si.getTextResId()); + assertEquals(null, si.getTextResName()); si = sorig.clone(/* flags=*/ 0); si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") @@ -614,6 +662,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(123, si.getRank()); assertEquals(null, si.getText()); assertEquals(1111, si.getTextResId()); + assertEquals(null, si.getTextResName()); si = sorig.clone(/* flags=*/ 0); si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") @@ -621,6 +670,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(123, si.getRank()); assertEquals("xxx", si.getDisabledMessage()); assertEquals(0, si.getDisabledMessageResourceId()); + assertEquals(null, si.getDisabledMessageResName()); si = sorig.clone(/* flags=*/ 0); si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") @@ -628,6 +678,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(123, si.getRank()); assertEquals(null, si.getDisabledMessage()); assertEquals(11111, si.getDisabledMessageResourceId()); + assertEquals(null, si.getDisabledMessageResName()); si = sorig.clone(/* flags=*/ 0); si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") @@ -731,7 +782,8 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(123, si.getRank()); assertEquals(1, si.getExtras().getInt("k")); - assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_FILE, si.getFlags()); + assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_FILE + | ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags()); assertNotNull(si.getBitmapPath()); // Something should be set. assertEquals(0, si.getIconResourceId()); assertTrue(si.getLastChangedTimestamp() < now); @@ -740,15 +792,14 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { public void testShortcutInfoSaveAndLoad_resId() throws InterruptedException { setCaller(CALLING_PACKAGE_1, USER_10); - final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource( - getTestContext().getResources(), R.drawable.black_32x32)); + final Icon res32x32 = Icon.createWithResource(mClientContext, R.drawable.black_32x32); PersistableBundle pb = new PersistableBundle(); pb.putInt("k", 1); ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext) .setId("id") .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class)) - .setIcon(bmp32x32) + .setIcon(res32x32) .setTitleResId(10) .setTextResId(11) .setDisabledMessageResId(12) @@ -778,17 +829,21 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName()); assertEquals(null, si.getIcon()); assertEquals(10, si.getTitleResId()); + assertEquals("r10", si.getTitleResName()); assertEquals(11, si.getTextResId()); + assertEquals("r11", si.getTextResName()); assertEquals(12, si.getDisabledMessageResourceId()); + assertEquals("r12", si.getDisabledMessageResName()); assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories()); assertEquals("action", si.getIntent().getAction()); assertEquals("val", si.getIntent().getStringExtra("key")); assertEquals(123, si.getRank()); assertEquals(1, si.getExtras().getInt("k")); - assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_FILE, si.getFlags()); - assertNotNull(si.getBitmapPath()); // Something should be set. - assertEquals(0, si.getIconResourceId()); + assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_RES + | ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags()); + assertNull(si.getBitmapPath()); + assertEquals(R.drawable.black_32x32, si.getIconResourceId()); assertTrue(si.getLastChangedTimestamp() < now); } @@ -840,7 +895,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(123, si.getRank()); assertEquals(1, si.getExtras().getInt("k")); - assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags()); + assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags()); assertNull(si.getBitmapPath()); // No icon. assertEquals(0, si.getIconResourceId()); } @@ -848,15 +903,14 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { public void testShortcutInfoSaveAndLoad_forBackup_resId() { setCaller(CALLING_PACKAGE_1, USER_0); - final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource( - getTestContext().getResources(), R.drawable.black_32x32)); + final Icon res32x32 = Icon.createWithResource(mClientContext, R.drawable.black_32x32); PersistableBundle pb = new PersistableBundle(); pb.putInt("k", 1); ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext) .setId("id") .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class)) - .setIcon(bmp32x32) + .setIcon(res32x32) .setTitleResId(10) .setTextResId(11) .setDisabledMessageResId(12) @@ -885,20 +939,23 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName()); assertEquals(null, si.getIcon()); assertEquals(10, si.getTitleResId()); + assertEquals("r10", si.getTitleResName()); assertEquals(11, si.getTextResId()); + assertEquals("r11", si.getTextResName()); assertEquals(12, si.getDisabledMessageResourceId()); + assertEquals("r12", si.getDisabledMessageResName()); assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories()); assertEquals("action", si.getIntent().getAction()); assertEquals("val", si.getIntent().getStringExtra("key")); assertEquals(123, si.getRank()); assertEquals(1, si.getExtras().getInt("k")); - assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags()); + assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags()); assertNull(si.getBitmapPath()); // No icon. assertEquals(0, si.getIconResourceId()); + assertEquals(null, si.getIconResName()); } - public void testThrottling() { final ShortcutInfo si1 = makeShortcut("shortcut1"); @@ -1086,11 +1143,6 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { final long origSequenceNumber = mService.getLocaleChangeSequenceNumber(); - // onSystemLocaleChangedNoLock before boot completed will be ignored. - mInternal.onSystemLocaleChangedNoLock(); - assertEquals(origSequenceNumber, mService.getLocaleChangeSequenceNumber()); - - mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); mInternal.onSystemLocaleChangedNoLock(); assertEquals(origSequenceNumber + 1, mService.getLocaleChangeSequenceNumber()); @@ -1467,4 +1519,103 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { }); } + + // Test for a ShortcutInfo method. + public void testGetResourcePackageName() { + assertEquals(null, ShortcutInfo.getResourcePackageName("")); + assertEquals(null, ShortcutInfo.getResourcePackageName("abc")); + assertEquals("p", ShortcutInfo.getResourcePackageName("p:")); + assertEquals("p", ShortcutInfo.getResourcePackageName("p:xx")); + assertEquals("pac", ShortcutInfo.getResourcePackageName("pac:")); + } + + // Test for a ShortcutInfo method. + public void testGetResourceTypeName() { + assertEquals(null, ShortcutInfo.getResourceTypeName("")); + assertEquals(null, ShortcutInfo.getResourceTypeName(":")); + assertEquals(null, ShortcutInfo.getResourceTypeName("/")); + assertEquals(null, ShortcutInfo.getResourceTypeName("/:")); + assertEquals("a", ShortcutInfo.getResourceTypeName(":a/")); + assertEquals("type", ShortcutInfo.getResourceTypeName("xxx:type/yyy")); + } + + // Test for a ShortcutInfo method. + public void testGetResourceTypeAndEntryName() { + assertEquals(null, ShortcutInfo.getResourceTypeAndEntryName("")); + assertEquals(null, ShortcutInfo.getResourceTypeAndEntryName("abc")); + assertEquals("", ShortcutInfo.getResourceTypeAndEntryName("p:")); + assertEquals("x", ShortcutInfo.getResourceTypeAndEntryName(":x")); + assertEquals("x", ShortcutInfo.getResourceTypeAndEntryName("p:x")); + assertEquals("xyz", ShortcutInfo.getResourceTypeAndEntryName("pac:xyz")); + } + + // Test for a ShortcutInfo method. + public void testGetResourceEntryName() { + assertEquals(null, ShortcutInfo.getResourceEntryName("")); + assertEquals(null, ShortcutInfo.getResourceEntryName("ab:")); + assertEquals("", ShortcutInfo.getResourceEntryName("/")); + assertEquals("abc", ShortcutInfo.getResourceEntryName("/abc")); + assertEquals("abc", ShortcutInfo.getResourceEntryName("xyz/abc")); + } + + // Test for a ShortcutInfo method. + public void testLookUpResourceName_systemResources() { + // For android system resources, lookUpResourceName will simply return the value as a + // string, regardless of "withType". + final Resources res = getTestContext().getResources(); + + assertEquals("" + android.R.string.cancel, ShortcutInfo.lookUpResourceName(res, + android.R.string.cancel, true, getTestContext().getPackageName())); + assertEquals("" + android.R.drawable.alert_dark_frame, ShortcutInfo.lookUpResourceName(res, + android.R.drawable.alert_dark_frame, true, getTestContext().getPackageName())); + assertEquals("" + android.R.string.cancel, ShortcutInfo.lookUpResourceName(res, + android.R.string.cancel, false, getTestContext().getPackageName())); + } + + public void testLookUpResourceName_appResources() { + final Resources res = getTestContext().getResources(); + + assertEquals("shortcut_text1", ShortcutInfo.lookUpResourceName(res, + R.string.shortcut_text1, false, getTestContext().getPackageName())); + assertEquals("string/shortcut_text1", ShortcutInfo.lookUpResourceName(res, + R.string.shortcut_text1, true, getTestContext().getPackageName())); + + assertEquals("black_16x64", ShortcutInfo.lookUpResourceName(res, + R.drawable.black_16x64, false, getTestContext().getPackageName())); + assertEquals("drawable/black_16x64", ShortcutInfo.lookUpResourceName(res, + R.drawable.black_16x64, true, getTestContext().getPackageName())); + } + + // Test for a ShortcutInfo method. + public void testLookUpResourceId_systemResources() { + final Resources res = getTestContext().getResources(); + + assertEquals(android.R.string.cancel, ShortcutInfo.lookUpResourceId(res, + "" + android.R.string.cancel, null, + getTestContext().getPackageName())); + assertEquals(android.R.drawable.alert_dark_frame, ShortcutInfo.lookUpResourceId(res, + "" + android.R.drawable.alert_dark_frame, null, + getTestContext().getPackageName())); + } + + // Test for a ShortcutInfo method. + public void testLookUpResourceId_appResources() { + final Resources res = getTestContext().getResources(); + + assertEquals(R.string.shortcut_text1, + ShortcutInfo.lookUpResourceId(res, "shortcut_text1", "string", + getTestContext().getPackageName())); + + assertEquals(R.string.shortcut_text1, + ShortcutInfo.lookUpResourceId(res, "string/shortcut_text1", null, + getTestContext().getPackageName())); + + assertEquals(R.drawable.black_16x64, + ShortcutInfo.lookUpResourceId(res, "black_16x64", "drawable", + getTestContext().getPackageName())); + + assertEquals(R.drawable.black_16x64, + ShortcutInfo.lookUpResourceId(res, "drawable/black_16x64", null, + getTestContext().getPackageName())); + } } |