Merge \\"ShortcutManager: deal with changing resource IDs on app update\\" into nyc-mr1-dev am: 3bb4366006

am: a3a56ab9d1

Change-Id: Id5e39a35db803223a89100a8e23a7b3309cd9dd3
This commit is contained in:
Makoto Onuki
2016-06-08 17:23:34 +00:00
committed by android-build-merger
10 changed files with 956 additions and 149 deletions

View File

@@ -10093,19 +10093,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";
}

View File

@@ -10516,19 +10516,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";
}

View File

@@ -10106,19 +10106,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";
}

View File

@@ -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_LAUNCHER = CLONE_REMOVE_ICON | CLONE_REMOVE_INTENT;
/** @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
| 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,36 +295,234 @@ 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;
}
}

View File

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

View File

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

View File

@@ -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++) {

View File

@@ -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);
}

View File

@@ -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.
});
}

View File

@@ -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()));
}
}