Merge changes Ibfb3cf6f,I9a4bd220 into rvc-dev
* changes:
Load Uri based shortcut drawable in LauncherApps
Adds icon URI field to shortcutInfo
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 8a89840..27c9cfc 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -103,4 +103,7 @@
in UserHandle user);
void uncacheShortcuts(String callingPackage, String packageName, in List<String> shortcutIds,
in UserHandle user);
+
+ String getShortcutIconUri(String callingPackage, String packageName, String shortcutId,
+ int userId);
}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 22516f0..e73fd03 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -51,6 +51,7 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -67,8 +68,10 @@
import android.util.Log;
import android.util.Pair;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.function.pooled.PooledLambda;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -1198,6 +1201,35 @@
}
/**
+ * @hide internal/unit tests only
+ */
+ @VisibleForTesting
+ public ParcelFileDescriptor getUriShortcutIconFd(@NonNull ShortcutInfo shortcut) {
+ return getUriShortcutIconFd(shortcut.getPackage(), shortcut.getId(), shortcut.getUserId());
+ }
+
+ private ParcelFileDescriptor getUriShortcutIconFd(@NonNull String packageName,
+ @NonNull String shortcutId, int userId) {
+ String uri = null;
+ try {
+ uri = mService.getShortcutIconUri(mContext.getPackageName(), packageName, shortcutId,
+ userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ if (uri == null) {
+ return null;
+ }
+ try {
+ return mContext.getContentResolver().openFileDescriptor(Uri.parse(uri), "r");
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Icon file not found: " + uri);
+ return null;
+ }
+ }
+
+ /**
* Returns the icon for this shortcut, without any badging for the profile.
*
* <p>The calling launcher application must be allowed to access the shortcut information,
@@ -1217,26 +1249,10 @@
public Drawable getShortcutIconDrawable(@NonNull ShortcutInfo shortcut, int density) {
if (shortcut.hasIconFile()) {
final ParcelFileDescriptor pfd = getShortcutIconFd(shortcut);
- if (pfd == null) {
- return null;
- }
- try {
- final Bitmap bmp = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
- if (bmp != null) {
- BitmapDrawable dr = new BitmapDrawable(mContext.getResources(), bmp);
- if (shortcut.hasAdaptiveBitmap()) {
- return new AdaptiveIconDrawable(null, dr);
- } else {
- return dr;
- }
- }
- return null;
- } finally {
- try {
- pfd.close();
- } catch (IOException ignore) {
- }
- }
+ return loadDrawableFromFileDescriptor(pfd, shortcut.hasAdaptiveBitmap());
+ } else if (shortcut.hasIconUri()) {
+ final ParcelFileDescriptor pfd = getUriShortcutIconFd(shortcut);
+ return loadDrawableFromFileDescriptor(pfd, shortcut.hasAdaptiveBitmap());
} else if (shortcut.hasIconResource()) {
return loadDrawableResourceFromPackage(shortcut.getPackage(),
shortcut.getIconResourceId(), shortcut.getUserHandle(), density);
@@ -1260,6 +1276,29 @@
}
}
+ private Drawable loadDrawableFromFileDescriptor(ParcelFileDescriptor pfd, boolean adaptive) {
+ if (pfd == null) {
+ return null;
+ }
+ try {
+ final Bitmap bmp = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
+ if (bmp != null) {
+ BitmapDrawable dr = new BitmapDrawable(mContext.getResources(), bmp);
+ if (adaptive) {
+ return new AdaptiveIconDrawable(null, dr);
+ } else {
+ return dr;
+ }
+ }
+ return null;
+ } finally {
+ try {
+ pfd.close();
+ } catch (IOException ignore) {
+ }
+ }
+ }
+
private Drawable loadDrawableResourceFromPackage(String packageName, int resId,
UserHandle user, int density) {
try {
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index af87578..8d8776f 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -123,6 +123,9 @@
public static final int FLAG_CACHED = 1 << 14;
/** @hide */
+ public static final int FLAG_HAS_ICON_URI = 1 << 15;
+
+ /** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
FLAG_DYNAMIC,
FLAG_PINNED,
@@ -139,6 +142,7 @@
FLAG_SHADOW,
FLAG_LONG_LIVED,
FLAG_CACHED,
+ FLAG_HAS_ICON_URI,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ShortcutFlags {}
@@ -401,6 +405,9 @@
private String mIconResName;
// Internal use only.
+ private String mIconUri;
+
+ // Internal use only.
@Nullable
private String mBitmapPath;
@@ -554,6 +561,7 @@
if ((cloneFlags & CLONE_REMOVE_ICON) == 0) {
mIcon = source.mIcon;
mBitmapPath = source.mBitmapPath;
+ mIconUri = source.mIconUri;
}
mTitle = source.mTitle;
@@ -856,6 +864,7 @@
mIconResId = 0;
mIconResName = null;
mBitmapPath = null;
+ mIconUri = null;
}
if (source.mTitle != null) {
mTitle = source.mTitle;
@@ -916,6 +925,8 @@
case Icon.TYPE_RESOURCE:
case Icon.TYPE_BITMAP:
case Icon.TYPE_ADAPTIVE_BITMAP:
+ case Icon.TYPE_URI:
+ case Icon.TYPE_URI_ADAPTIVE_BITMAP:
break; // OK
default:
throw getInvalidIconException();
@@ -1792,6 +1803,15 @@
return hasFlags(FLAG_HAS_ICON_RES);
}
+ /**
+ * Return whether a shortcut's icon is provided via a URI.
+ *
+ * @hide internal/unit tests only
+ */
+ public boolean hasIconUri() {
+ return hasFlags(FLAG_HAS_ICON_URI);
+ }
+
/** @hide */
public boolean hasStringResources() {
return (mTitleResId != 0) || (mTextResId != 0) || (mDisabledMessageResId != 0);
@@ -1911,6 +1931,19 @@
return mIconResId;
}
+ /** @hide */
+ public void setIconUri(String iconUri) {
+ mIconUri = iconUri;
+ }
+
+ /**
+ * Get the Uri for the icon, valid only when {@link #hasIconUri()} } is true.
+ * @hide internal / tests only.
+ */
+ public String getIconUri() {
+ return mIconUri;
+ }
+
/**
* Bitmap path. Note this will be null even if {@link #hasIconFile()} is set when the save
* is pending. Use {@link #isIconPendingSave()} to check it.
@@ -2062,6 +2095,7 @@
mPersons = source.readParcelableArray(cl, Person.class);
mLocusId = source.readParcelable(cl);
+ mIconUri = source.readString();
}
@Override
@@ -2112,6 +2146,7 @@
dest.writeParcelableArray(mPersons, flags);
dest.writeParcelable(mLocusId, flags);
+ dest.writeString(mIconUri);
}
public static final @android.annotation.NonNull Creator<ShortcutInfo> CREATOR =
@@ -2203,6 +2238,12 @@
if (hasIconResource()) {
sb.append("Ic-r");
}
+ if (hasIconUri()) {
+ sb.append("Ic-u");
+ }
+ if (hasAdaptiveBitmap()) {
+ sb.append("Ic-a");
+ }
if (hasKeyFieldsOnly()) {
sb.append("Key");
}
@@ -2325,6 +2366,9 @@
sb.append(", bitmapPath=");
sb.append(mBitmapPath);
+
+ sb.append(", iconUri=");
+ sb.append(mIconUri);
}
if (mLocusId != null) {
@@ -2343,8 +2387,8 @@
CharSequence disabledMessage, int disabledMessageResId, String disabledMessageResName,
Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras,
long lastChangedTimestamp,
- int flags, int iconResId, String iconResName, String bitmapPath, int disabledReason,
- Person[] persons, LocusId locusId) {
+ int flags, int iconResId, String iconResName, String bitmapPath, String iconUri,
+ int disabledReason, Person[] persons, LocusId locusId) {
mUserId = userId;
mId = id;
mPackageName = packageName;
@@ -2369,6 +2413,7 @@
mIconResId = iconResId;
mIconResName = iconResName;
mBitmapPath = bitmapPath;
+ mIconUri = iconUri;
mDisabledReason = disabledReason;
mPersons = persons;
mLocusId = locusId;
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index a50ce92..435c70a 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -103,4 +103,10 @@
*/
public abstract List<ShortcutManager.ShareShortcutInfo> getShareTargets(
@NonNull String callingPackage, @NonNull IntentFilter intentFilter, int userId);
+
+ /**
+ * Returns the icon Uri of the shortcut, and grants Uri read permission to the caller.
+ */
+ public abstract String getShortcutIconUri(int launcherUserId, @NonNull String launcherPackage,
+ @NonNull String packageName, @NonNull String shortcutId, int userId);
}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 8031eaa..1b271a7 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -819,6 +819,18 @@
}
@Override
+ public String getShortcutIconUri(String callingPackage, String packageName,
+ String shortcutId, int userId) {
+ ensureShortcutPermission(callingPackage);
+ if (!canAccessProfile(userId, "Cannot access shortcuts")) {
+ return null;
+ }
+
+ return mShortcutServiceInternal.getShortcutIconUri(getCallingUserId(), callingPackage,
+ packageName, shortcutId, userId);
+ }
+
+ @Override
public boolean hasShortcutHostPermission(String callingPackage) {
verifyCallingPackage(callingPackage);
return mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
diff --git a/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java b/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java
index dc534a7..1c5f0a7 100644
--- a/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java
+++ b/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java
@@ -28,7 +28,6 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
import com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
import libcore.io.IoUtils;
@@ -150,9 +149,10 @@
shortcut.setIconResourceId(0);
shortcut.setIconResName(null);
shortcut.setBitmapPath(null);
+ shortcut.setIconUri(null);
shortcut.clearFlags(ShortcutInfo.FLAG_HAS_ICON_FILE |
ShortcutInfo.FLAG_ADAPTIVE_BITMAP | ShortcutInfo.FLAG_HAS_ICON_RES |
- ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE);
+ ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE | ShortcutInfo.FLAG_HAS_ICON_URI);
}
public void saveBitmapLocked(ShortcutInfo shortcut,
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index c8df5c7..1fc0a38 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -102,6 +102,7 @@
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 ATTR_ICON_URI = "icon-uri";
private static final String ATTR_LOCUS_ID = "locus-id";
private static final String ATTR_PERSON_NAME = "name";
@@ -1628,7 +1629,8 @@
int flags = si.getFlags() &
~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES
| ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE
- | ShortcutInfo.FLAG_DYNAMIC);
+ | ShortcutInfo.FLAG_DYNAMIC
+ | ShortcutInfo.FLAG_HAS_ICON_URI | ShortcutInfo.FLAG_ADAPTIVE_BITMAP);
ShortcutService.writeAttr(out, ATTR_FLAGS, flags);
// Set the publisher version code at every backup.
@@ -1646,6 +1648,7 @@
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());
+ ShortcutService.writeAttr(out, ATTR_ICON_URI, si.getIconUri());
}
if (shouldBackupDetails) {
@@ -1764,6 +1767,7 @@
int iconResId;
String iconResName;
String bitmapPath;
+ String iconUri;
final String locusIdString;
int backupVersionCode;
ArraySet<String> categories = null;
@@ -1791,6 +1795,7 @@
iconResId = (int) ShortcutService.parseLongAttribute(parser, ATTR_ICON_RES_ID);
iconResName = ShortcutService.parseStringAttribute(parser, ATTR_ICON_RES_NAME);
bitmapPath = ShortcutService.parseStringAttribute(parser, ATTR_BITMAP_PATH);
+ iconUri = ShortcutService.parseStringAttribute(parser, ATTR_ICON_URI);
locusIdString = ShortcutService.parseStringAttribute(parser, ATTR_LOCUS_ID);
final int outerDepth = parser.getDepth();
@@ -1866,8 +1871,8 @@
categories,
intents.toArray(new Intent[intents.size()]),
rank, extras, lastChangedTimestamp, flags,
- iconResId, iconResName, bitmapPath, disabledReason,
- persons.toArray(new Person[persons.size()]), locusId);
+ iconResId, iconResName, bitmapPath, iconUri,
+ disabledReason, persons.toArray(new Person[persons.size()]), locusId);
}
private static Intent parseIntent(XmlPullParser parser)
@@ -1991,16 +1996,26 @@
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " still has an icon");
}
- if (si.hasAdaptiveBitmap() && !si.hasIconFile()) {
+ if (si.hasAdaptiveBitmap() && !(si.hasIconFile() || si.hasIconUri())) {
failed = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
- + " has adaptive bitmap but was not saved to a file.");
+ + " has adaptive bitmap but was not saved to a file nor has icon uri.");
}
if (si.hasIconFile() && si.hasIconResource()) {
failed = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has both resource and bitmap icons");
}
+ if (si.hasIconFile() && si.hasIconUri()) {
+ failed = true;
+ Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ + " has both url and bitmap icons");
+ }
+ if (si.hasIconUri() && si.hasIconResource()) {
+ failed = true;
+ Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ + " has both url and resource icons");
+ }
if (si.isEnabled()
!= (si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED)) {
failed = true;
diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java
index f9c0db0..d3aace1 100644
--- a/services/core/java/com/android/server/pm/ShortcutParser.java
+++ b/services/core/java/com/android/server/pm/ShortcutParser.java
@@ -449,6 +449,7 @@
iconResId,
null, // icon res name
null, // bitmap path
+ null, // icon Url
disabledReason,
null /* persons */,
null /* locusId */);
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 66f3574..8768ab0 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -24,6 +24,8 @@
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.IUidObserver;
+import android.app.IUriGrantsManager;
+import android.app.UriGrantsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.appwidget.AppWidgetProviderInfo;
import android.content.BroadcastReceiver;
@@ -65,6 +67,7 @@
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
+import android.os.IBinder;
import android.os.LocaleList;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
@@ -106,6 +109,7 @@
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.ShortcutUser.PackageWithUser;
+import com.android.server.uri.UriGrantsManagerInternal;
import libcore.io.IoUtils;
@@ -327,6 +331,9 @@
private final UserManagerInternal mUserManagerInternal;
private final UsageStatsManagerInternal mUsageStatsManagerInternal;
private final ActivityManagerInternal mActivityManagerInternal;
+ private final IUriGrantsManager mUriGrantsManager;
+ private final UriGrantsManagerInternal mUriGrantsManagerInternal;
+ private final IBinder mUriPermissionOwner;
private final ShortcutRequestPinProcessor mShortcutRequestPinProcessor;
private final ShortcutBitmapSaver mShortcutBitmapSaver;
@@ -449,6 +456,11 @@
mActivityManagerInternal = Objects.requireNonNull(
LocalServices.getService(ActivityManagerInternal.class));
+ mUriGrantsManager = UriGrantsManager.getService();
+ mUriGrantsManagerInternal = Objects.requireNonNull(
+ LocalServices.getService(UriGrantsManagerInternal.class));
+ mUriPermissionOwner = mUriGrantsManagerInternal.newUriPermissionOwner(TAG);
+
mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mLock);
mShortcutBitmapSaver = new ShortcutBitmapSaver(this);
mShortcutDumpFiles = new ShortcutDumpFiles(this);
@@ -1414,7 +1426,7 @@
}
void saveIconAndFixUpShortcutLocked(ShortcutInfo shortcut) {
- if (shortcut.hasIconFile() || shortcut.hasIconResource()) {
+ if (shortcut.hasIconFile() || shortcut.hasIconResource() || shortcut.hasIconUri()) {
return;
}
@@ -1438,6 +1450,15 @@
shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_RES);
return;
}
+ case Icon.TYPE_URI:
+ shortcut.setIconUri(icon.getUriString());
+ shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_URI);
+ return;
+ case Icon.TYPE_URI_ADAPTIVE_BITMAP:
+ shortcut.setIconUri(icon.getUriString());
+ shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_URI
+ | ShortcutInfo.FLAG_ADAPTIVE_BITMAP);
+ return;
case Icon.TYPE_BITMAP:
bitmap = icon.getBitmap(); // Don't recycle in this case.
break;
@@ -3129,6 +3150,59 @@
}
@Override
+ public String getShortcutIconUri(int launcherUserId, @NonNull String launcherPackage,
+ @NonNull String packageName, @NonNull String shortcutId, int userId) {
+ Objects.requireNonNull(launcherPackage, "launcherPackage");
+ Objects.requireNonNull(packageName, "packageName");
+ Objects.requireNonNull(shortcutId, "shortcutId");
+
+ synchronized (mLock) {
+ throwIfUserLockedL(userId);
+ throwIfUserLockedL(launcherUserId);
+
+ getLauncherShortcutsLocked(launcherPackage, userId, launcherUserId)
+ .attemptToRestoreIfNeededAndSave();
+
+ final ShortcutPackage p = getUserShortcutsLocked(userId)
+ .getPackageShortcutsIfExists(packageName);
+ if (p == null) {
+ return null;
+ }
+
+ final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId);
+ if (shortcutInfo == null || !shortcutInfo.hasIconUri()) {
+ return null;
+ }
+ String uri = shortcutInfo.getIconUri();
+ if (uri == null) {
+ Slog.w(TAG, "null uri detected in getShortcutIconUri()");
+ return null;
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ int packageUid = mPackageManagerInternal.getPackageUidInternal(packageName,
+ PackageManager.MATCH_DIRECT_BOOT_AUTO, userId);
+ // Grant read uri permission to the caller on behalf of the shortcut owner. All
+ // granted permissions are revoked when the default launcher changes, or when
+ // device is rebooted.
+ // b/151572645 is tracking a bug where Uri permissions are persisted across
+ // reboots, even when Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION is not used.
+ mUriGrantsManager.grantUriPermissionFromOwner(mUriPermissionOwner, packageUid,
+ launcherPackage, Uri.parse(uri), Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ userId, launcherUserId);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to grant uri access to " + launcherPackage + " for " + uri,
+ e);
+ uri = null;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return uri;
+ }
+ }
+
+ @Override
public boolean hasShortcutHostPermission(int launcherUserId,
@NonNull String callingPackage, int callingPid, int callingUid) {
return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId,
@@ -3241,7 +3315,14 @@
user.clearLauncher();
}
if (Intent.ACTION_PREFERRED_ACTIVITY_CHANGED.equals(action)) {
- // Nothing farther to do.
+ final ShortcutUser user = getUserShortcutsLocked(userId);
+ final ComponentName lastLauncher = user.getLastKnownLauncher();
+ final ComponentName currentLauncher = getDefaultLauncher(userId);
+ if (currentLauncher == null || !currentLauncher.equals(lastLauncher)) {
+ // Default launcher is removed or changed, revoke all URI permissions.
+ mUriGrantsManagerInternal.revokeUriPermissionFromOwner(mUriPermissionOwner,
+ null, ~0, 0);
+ }
return;
}
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 6c769485..6a88298 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -94,6 +94,8 @@
import com.android.server.SystemService;
import com.android.server.pm.LauncherAppsService.LauncherAppsImpl;
import com.android.server.pm.ShortcutUser.PackageWithUser;
+import com.android.server.uri.UriGrantsManagerInternal;
+import com.android.server.uri.UriPermissionOwner;
import com.android.server.wm.ActivityTaskManagerInternal;
import org.junit.Assert;
@@ -630,6 +632,9 @@
protected UsageStatsManagerInternal mMockUsageStatsManagerInternal;
protected ActivityManagerInternal mMockActivityManagerInternal;
protected ActivityTaskManagerInternal mMockActivityTaskManagerInternal;
+ protected UriGrantsManagerInternal mMockUriGrantsManagerInternal;
+
+ protected UriPermissionOwner mUriPermissionOwner;
protected static final String CALLING_PACKAGE_1 = "com.android.test.1";
protected static final int CALLING_UID_1 = 10001;
@@ -771,6 +776,7 @@
mMockUsageStatsManagerInternal = mock(UsageStatsManagerInternal.class);
mMockActivityManagerInternal = mock(ActivityManagerInternal.class);
mMockActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class);
+ mMockUriGrantsManagerInternal = mock(UriGrantsManagerInternal.class);
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
@@ -782,6 +788,10 @@
LocalServices.addService(ActivityTaskManagerInternal.class, mMockActivityTaskManagerInternal);
LocalServices.removeServiceForTest(UserManagerInternal.class);
LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
+ LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
+ LocalServices.addService(UriGrantsManagerInternal.class, mMockUriGrantsManagerInternal);
+
+ mUriPermissionOwner = new UriPermissionOwner(mMockUriGrantsManagerInternal, TAG);
// Prepare injection values.
@@ -2193,6 +2203,7 @@
for (ShortcutInfo s : actualShortcuts) {
assertTrue("ID " + s.getId() + " not have icon res ID", s.hasIconResource());
assertFalse("ID " + s.getId() + " shouldn't have icon FD", s.hasIconFile());
+ assertFalse("ID " + s.getId() + " shouldn't have icon URI", s.hasIconUri());
}
return actualShortcuts;
}
@@ -2202,6 +2213,17 @@
for (ShortcutInfo s : actualShortcuts) {
assertFalse("ID " + s.getId() + " shouldn't have icon res ID", s.hasIconResource());
assertTrue("ID " + s.getId() + " not have icon FD", s.hasIconFile());
+ assertFalse("ID " + s.getId() + " shouldn't have icon URI", s.hasIconUri());
+ }
+ return actualShortcuts;
+ }
+
+ public static List<ShortcutInfo> assertAllHaveIconUri(
+ List<ShortcutInfo> actualShortcuts) {
+ for (ShortcutInfo s : actualShortcuts) {
+ assertFalse("ID " + s.getId() + " shouldn't have icon res ID", s.hasIconResource());
+ assertFalse("ID " + s.getId() + " shouldn't have have icon FD", s.hasIconFile());
+ assertTrue("ID " + s.getId() + " not have icon URI", s.hasIconUri());
}
return actualShortcuts;
}
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 56460fb..2cbb6d5 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -114,8 +114,11 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.InputStreamReader;
+import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@@ -133,6 +136,16 @@
*/
@SmallTest
public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
+
+ @Override
+ protected void tearDown() throws Exception {
+ deleteUriFile("file32x32.jpg");
+ deleteUriFile("file64x64.jpg");
+ deleteUriFile("file512x512.jpg");
+
+ super.tearDown();
+ }
+
/**
* Test for the first launch path, no settings file available.
*/
@@ -724,6 +737,17 @@
final Icon bmp512x512 = Icon.createWithBitmap(BitmapFactory.decodeResource(
getTestContext().getResources(), R.drawable.black_512x512));
+ // The corresponding files will be deleted in tearDown()
+ final Icon uri32x32 = Icon.createWithContentUri(
+ getFileUriFromResource("file32x32.jpg", R.drawable.black_32x32));
+ final Icon uri64x64_maskable = Icon.createWithAdaptiveBitmapContentUri(
+ getFileUriFromResource("file64x64.jpg", R.drawable.black_64x64));
+ final Icon uri512x512 = Icon.createWithContentUri(
+ getFileUriFromResource("file512x512.jpg", R.drawable.black_512x512));
+
+ doReturn(mUriPermissionOwner.getExternalToken())
+ .when(mMockUriGrantsManagerInternal).newUriPermissionOwner(anyString());
+
// Set from package 1
setCaller(CALLING_PACKAGE_1);
assertTrue(mManager.setDynamicShortcuts(list(
@@ -732,6 +756,9 @@
makeShortcutWithIcon("bmp32x32", bmp32x32),
makeShortcutWithIcon("bmp64x64", bmp64x64_maskable),
makeShortcutWithIcon("bmp512x512", bmp512x512),
+ makeShortcutWithIcon("uri32x32", uri32x32),
+ makeShortcutWithIcon("uri64x64", uri64x64_maskable),
+ makeShortcutWithIcon("uri512x512", uri512x512),
makeShortcut("none")
)));
@@ -742,6 +769,9 @@
"bmp32x32",
"bmp64x64",
"bmp512x512",
+ "uri32x32",
+ "uri64x64",
+ "uri512x512",
"none");
// Call from another caller with the same ID, just to make sure storage is per-package.
@@ -749,11 +779,15 @@
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcutWithIcon("res32x32", res512x512),
makeShortcutWithIcon("res64x64", res512x512),
+ makeShortcutWithIcon("uri32x32", uri512x512),
+ makeShortcutWithIcon("uri64x64", uri512x512),
makeShortcutWithIcon("none", res512x512)
)));
assertShortcutIds(assertAllNotHaveIcon(mManager.getDynamicShortcuts()),
"res32x32",
"res64x64",
+ "uri32x32",
+ "uri64x64",
"none");
// Different profile. Note the names and the contents don't match.
@@ -795,6 +829,18 @@
list(getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp512x512", USER_0))),
"bmp512x512");
+ assertShortcutIds(assertAllHaveIconUri(
+ list(getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "uri32x32", USER_0))),
+ "uri32x32");
+
+ assertShortcutIds(assertAllHaveIconUri(
+ list(getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "uri64x64", USER_0))),
+ "uri64x64");
+
+ assertShortcutIds(assertAllHaveIconUri(
+ list(getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "uri512x512", USER_0))),
+ "uri512x512");
+
assertShortcutIds(assertAllHaveIconResId(
list(getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "res32x32", USER_P0))),
"res32x32");
@@ -860,15 +906,37 @@
bmp = pfdToBitmap(
mLauncherApps.getShortcutIconFd(CALLING_PACKAGE_1, "bmp32x32", HANDLE_USER_P0));
assertBitmapSize(128, 128, bmp);
+/*
+ bmp = pfdToBitmap(mLauncherApps.getUriShortcutIconFd(
+ getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "uri32x32", USER_0)));
+ assertBitmapSize(32, 32, bmp);
- Drawable dr = mLauncherApps.getShortcutIconDrawable(
+ bmp = pfdToBitmap(mLauncherApps.getUriShortcutIconFd(
+ getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "uri64x64", USER_0)));
+ assertBitmapSize(64, 64, bmp);
+
+ bmp = pfdToBitmap(mLauncherApps.getUriShortcutIconFd(
+ getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "uri512x512", USER_0)));
+ assertBitmapSize(512, 512, bmp);
+*/
+
+ Drawable dr_bmp = mLauncherApps.getShortcutIconDrawable(
makeShortcutWithIcon("bmp64x64", bmp64x64_maskable), 0);
- assertTrue(dr instanceof AdaptiveIconDrawable);
+ assertTrue(dr_bmp instanceof AdaptiveIconDrawable);
float viewportPercentage = 1 / (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction());
assertEquals((int) (bmp64x64_maskable.getBitmap().getWidth() * viewportPercentage),
- dr.getIntrinsicWidth());
+ dr_bmp.getIntrinsicWidth());
assertEquals((int) (bmp64x64_maskable.getBitmap().getHeight() * viewportPercentage),
- dr.getIntrinsicHeight());
+ dr_bmp.getIntrinsicHeight());
+/*
+ Drawable dr_uri = mLauncherApps.getShortcutIconDrawable(
+ makeShortcutWithIcon("uri64x64", uri64x64_maskable), 0);
+ assertTrue(dr_uri instanceof AdaptiveIconDrawable);
+ assertEquals((int) (bmp64x64_maskable.getBitmap().getWidth() * viewportPercentage),
+ dr_uri.getIntrinsicWidth());
+ assertEquals((int) (bmp64x64_maskable.getBitmap().getHeight() * viewportPercentage),
+ dr_uri.getIntrinsicHeight());
+*/
}
public void testCleanupDanglingBitmaps() throws Exception {
@@ -1274,6 +1342,18 @@
makeShortcut("s1")
)));
+ // Set uri icon
+ assertTrue(mManager.updateShortcuts(list(
+ new ShortcutInfo.Builder(mClientContext, "s1")
+ .setIcon(Icon.createWithContentUri("test_uri"))
+ .build()
+ )));
+ mService.waitForBitmapSavesForTest();
+ assertWith(getCallerShortcuts())
+ .forShortcutWithId("s1", si -> {
+ assertTrue(si.hasIconUri());
+ assertEquals("test_uri", si.getIconUri());
+ });
// Set resource icon
assertTrue(mManager.updateShortcuts(list(
new ShortcutInfo.Builder(mClientContext, "s1")
@@ -1287,6 +1367,9 @@
assertEquals(R.drawable.black_32x32, si.getIconResourceId());
});
mService.waitForBitmapSavesForTest();
+
+ mInjectedCurrentTimeMillis += INTERVAL; // reset throttling
+
// Set bitmap icon
assertTrue(mManager.updateShortcuts(list(
new ShortcutInfo.Builder(mClientContext, "s1")
@@ -1300,9 +1383,7 @@
assertTrue(si.hasIconFile());
});
- mInjectedCurrentTimeMillis += INTERVAL; // reset throttling
-
- // Do it again, with the reverse order (bitmap -> icon)
+ // Do it again, with the reverse order (bitmap -> resource -> uri)
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcut("s1")
)));
@@ -1320,6 +1401,8 @@
assertTrue(si.hasIconFile());
});
+ mInjectedCurrentTimeMillis += INTERVAL; // reset throttling
+
// Set resource icon
assertTrue(mManager.updateShortcuts(list(
new ShortcutInfo.Builder(mClientContext, "s1")
@@ -1332,6 +1415,18 @@
assertTrue(si.hasIconResource());
assertEquals(R.drawable.black_32x32, si.getIconResourceId());
});
+ // Set uri icon
+ assertTrue(mManager.updateShortcuts(list(
+ new ShortcutInfo.Builder(mClientContext, "s1")
+ .setIcon(Icon.createWithContentUri("test_uri"))
+ .build()
+ )));
+ mService.waitForBitmapSavesForTest();
+ assertWith(getCallerShortcuts())
+ .forShortcutWithId("s1", si -> {
+ assertTrue(si.hasIconUri());
+ assertEquals("test_uri", si.getIconUri());
+ });
});
}
@@ -8499,4 +8594,26 @@
}
}
}
+
+ private Uri getFileUriFromResource(String fileName, int resId) throws IOException {
+ File file = new File(getTestContext().getFilesDir(), fileName);
+ // Make sure we are not leaving phantom files behind.
+ assertFalse(file.exists());
+ try (InputStream source = getTestContext().getResources().openRawResource(resId);
+ OutputStream target = new FileOutputStream(file)) {
+ byte[] buffer = new byte[1024];
+ for (int len = source.read(buffer); len >= 0; len = source.read(buffer)) {
+ target.write(buffer, 0, len);
+ }
+ }
+ assertTrue(file.exists());
+ return Uri.fromFile(file);
+ }
+
+ private void deleteUriFile(String fileName) {
+ File file = new File(getTestContext().getFilesDir(), fileName);
+ if (file.exists()) {
+ file.delete();
+ }
+ }
}
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 7b101c7..ca77049 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -257,6 +257,7 @@
si.addFlags(ShortcutInfo.FLAG_PINNED);
si.setBitmapPath("abc");
si.setIconResourceId(456);
+ si.setIconUri("test_uri");
si = parceled(si);
@@ -279,6 +280,7 @@
assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_LONG_LIVED, si.getFlags());
assertEquals("abc", si.getBitmapPath());
assertEquals(456, si.getIconResourceId());
+ assertEquals("test_uri", si.getIconUri());
assertEquals(0, si.getTitleResId());
assertEquals(null, si.getTitleResName());
@@ -310,6 +312,7 @@
si.addFlags(ShortcutInfo.FLAG_PINNED);
si.setBitmapPath("abc");
si.setIconResourceId(456);
+ si.setIconUri("test_uri");
lookupAndFillInResourceNames(si);
@@ -335,6 +338,7 @@
assertEquals("abc", si.getBitmapPath());
assertEquals(456, si.getIconResourceId());
assertEquals("string/r456", si.getIconResName());
+ assertEquals("test_uri", si.getIconUri());
}
public void testShortcutInfoClone() {
@@ -359,6 +363,7 @@
sorig.addFlags(ShortcutInfo.FLAG_PINNED);
sorig.setBitmapPath("abc");
sorig.setIconResourceId(456);
+ sorig.setIconUri("test_uri");
lookupAndFillInResourceNames(sorig);
@@ -386,6 +391,7 @@
assertEquals("abc", si.getBitmapPath());
assertEquals(456, si.getIconResourceId());
assertEquals("string/r456", si.getIconResName());
+ assertEquals("test_uri", si.getIconUri());
si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
@@ -407,6 +413,7 @@
assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_LONG_LIVED, si.getFlags());
assertEquals(null, si.getBitmapPath());
+ assertNull(si.getIconUri());
assertEquals(456, si.getIconResourceId());
assertEquals(null, si.getIconResName());
@@ -428,6 +435,7 @@
assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_LONG_LIVED, si.getFlags());
assertEquals(null, si.getBitmapPath());
+ assertNull(si.getIconUri());
assertEquals(456, si.getIconResourceId());
assertEquals(null, si.getIconResName());
@@ -450,6 +458,7 @@
assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_KEY_FIELDS_ONLY
| ShortcutInfo.FLAG_LONG_LIVED, si.getFlags());
assertEquals(null, si.getBitmapPath());
+ assertNull(si.getIconUri());
assertEquals(456, si.getIconResourceId());
assertEquals(null, si.getIconResName());
@@ -474,6 +483,7 @@
assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_LONG_LIVED, si.getFlags());
assertEquals(null, si.getBitmapPath());
+ assertNull(si.getIconUri());
assertEquals(456, si.getIconResourceId());
assertEquals(null, si.getIconResName());
@@ -499,6 +509,7 @@
sorig.addFlags(ShortcutInfo.FLAG_PINNED);
sorig.setBitmapPath("abc");
sorig.setIconResourceId(456);
+ sorig.setIconUri("test_uri");
lookupAndFillInResourceNames(sorig);
@@ -526,6 +537,7 @@
assertEquals("abc", si.getBitmapPath());
assertEquals(456, si.getIconResourceId());
assertEquals("string/r456", si.getIconResName());
+ assertEquals("test_uri", si.getIconUri());
si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
@@ -547,6 +559,7 @@
assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
assertEquals(null, si.getBitmapPath());
+ assertNull(si.getIconUri());
assertEquals(456, si.getIconResourceId());
assertEquals(null, si.getIconResName());
@@ -570,6 +583,7 @@
assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
assertEquals(null, si.getBitmapPath());
+ assertNull(si.getIconUri());
assertEquals(456, si.getIconResourceId());
assertEquals(null, si.getIconResName());
@@ -593,6 +607,7 @@
assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_KEY_FIELDS_ONLY, si.getFlags());
assertEquals(null, si.getBitmapPath());
+ assertNull(si.getIconUri());
assertEquals(456, si.getIconResourceId());
assertEquals(null, si.getIconResName());
@@ -657,6 +672,7 @@
sorig.addFlags(ShortcutInfo.FLAG_PINNED);
sorig.setBitmapPath("abc");
sorig.setIconResourceId(456);
+ sorig.setIconUri("test_uri");
lookupAndFillInResourceNames(sorig);
@@ -677,6 +693,7 @@
assertEquals(0, si.getIconResourceId());
assertEquals(null, si.getIconResName());
assertEquals(null, si.getBitmapPath());
+ assertNull(si.getIconUri());
si = sorig.clone(/* flags=*/ 0);
si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
@@ -787,6 +804,7 @@
sorig.addFlags(ShortcutInfo.FLAG_PINNED);
sorig.setBitmapPath("abc");
sorig.setIconResourceId(456);
+ sorig.setIconUri("test_uri");
ShortcutInfo si;
@@ -804,6 +822,7 @@
assertEquals(0, si.getIconResourceId());
assertEquals(null, si.getIconResName());
assertEquals(null, si.getBitmapPath());
+ assertNull(si.getIconUri());
si = sorig.clone(/* flags=*/ 0);
si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
@@ -977,6 +996,7 @@
| ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags());
assertNotNull(si.getBitmapPath()); // Something should be set.
assertEquals(0, si.getIconResourceId());
+ assertNull(si.getIconUri());
assertTrue(si.getLastChangedTimestamp() < now);
// Make sure ranks are saved too. Because of the auto-adjusting, we need two shortcuts
@@ -1053,6 +1073,7 @@
si.getFlags());
assertNotNull(si.getBitmapPath()); // Something should be set.
assertEquals(0, si.getIconResourceId());
+ assertNull(si.getIconUri());
assertTrue(si.getLastChangedTimestamp() < now);
dumpUserFile(USER_10);
@@ -1125,6 +1146,7 @@
assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_RES
| ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags());
assertNull(si.getBitmapPath());
+ assertNull(si.getIconUri());
assertEquals(R.drawable.black_32x32, si.getIconResourceId());
assertTrue(si.getLastChangedTimestamp() < now);
@@ -1134,6 +1156,94 @@
assertEquals(1, si.getRank());
}
+ public void testShortcutInfoSaveAndLoad_uri() throws InterruptedException {
+ mRunningUsers.put(USER_10, true);
+
+ setCaller(CALLING_PACKAGE_1, USER_10);
+
+ final Icon uriIcon = Icon.createWithContentUri("test_uri");
+
+ PersistableBundle pb = new PersistableBundle();
+ pb.putInt("k", 1);
+ ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
+ .setId("id")
+ .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
+ .setIcon(uriIcon)
+ .setTitleResId(10)
+ .setTextResId(11)
+ .setDisabledMessageResId(12)
+ .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setRank(123)
+ .setExtras(pb)
+ .build();
+ sorig.setTimestamp(mInjectedCurrentTimeMillis);
+
+ final Icon uriMaskableIcon = Icon.createWithAdaptiveBitmapContentUri("uri_maskable");
+
+ ShortcutInfo sorig2 = new ShortcutInfo.Builder(mClientContext)
+ .setId("id2")
+ .setTitle("x")
+ .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setRank(456)
+ .setIcon(uriMaskableIcon)
+ .build();
+ sorig2.setTimestamp(mInjectedCurrentTimeMillis);
+
+ mManager.addDynamicShortcuts(list(sorig, sorig2));
+
+ mInjectedCurrentTimeMillis += 1;
+ final long now = mInjectedCurrentTimeMillis;
+ mInjectedCurrentTimeMillis += 1;
+
+ // Save and load.
+ mService.saveDirtyInfo();
+ initService();
+ mService.handleUnlockUser(USER_10);
+
+ ShortcutInfo si;
+ si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_10);
+
+ assertEquals(USER_10, si.getUserId());
+ assertEquals(HANDLE_USER_10, si.getUserHandle());
+ assertEquals(CALLING_PACKAGE_1, si.getPackage());
+ assertEquals("id", si.getId());
+ 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(0, si.getRank());
+ assertEquals(1, si.getExtras().getInt("k"));
+
+ assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_URI
+ | ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags());
+ assertNull(si.getBitmapPath());
+ assertNull(si.getIconResName());
+ assertEquals(0, si.getIconResourceId());
+ assertEquals("test_uri", si.getIconUri());
+ assertTrue(si.getLastChangedTimestamp() < now);
+
+ // Make sure ranks are saved too. Because of the auto-adjusting, we need two shortcuts
+ // to test it.
+ si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_10);
+ assertEquals(1, si.getRank());
+ assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_URI
+ | ShortcutInfo.FLAG_STRINGS_RESOLVED | ShortcutInfo.FLAG_ADAPTIVE_BITMAP,
+ si.getFlags());
+ assertNull(si.getBitmapPath());
+ assertNull(si.getIconResName());
+ assertEquals(0, si.getIconResourceId());
+ assertEquals("uri_maskable", si.getIconUri());
+ }
+
public void testShortcutInfoSaveAndLoad_forBackup() {
setCaller(CALLING_PACKAGE_1, USER_0);
@@ -1196,6 +1306,7 @@
| ShortcutInfo.FLAG_SHADOW , si.getFlags());
assertNull(si.getBitmapPath()); // No icon.
assertEquals(0, si.getIconResourceId());
+ assertNull(si.getIconUri());
// Note when restored from backup, it's no longer dynamic, so shouldn't have a rank.
si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_0);
@@ -1265,6 +1376,77 @@
assertNull(si.getBitmapPath()); // No icon.
assertEquals(0, si.getIconResourceId());
assertEquals(null, si.getIconResName());
+ assertNull(si.getIconUri());
+
+ // Note when restored from backup, it's no longer dynamic, so shouldn't have a rank.
+ si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_0);
+ assertEquals(0, si.getRank());
+ }
+
+ public void testShortcutInfoSaveAndLoad_forBackup_uri() {
+ setCaller(CALLING_PACKAGE_1, USER_0);
+
+ final Icon uriIcon = Icon.createWithContentUri("test_uri");
+
+ PersistableBundle pb = new PersistableBundle();
+ pb.putInt("k", 1);
+ ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
+ .setId("id")
+ .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
+ .setIcon(uriIcon)
+ .setTitleResId(10)
+ .setTextResId(11)
+ .setDisabledMessageResId(12)
+ .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setRank(123)
+ .setExtras(pb)
+ .build();
+
+ ShortcutInfo sorig2 = new ShortcutInfo.Builder(mClientContext)
+ .setId("id2")
+ .setTitle("x")
+ .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setRank(456)
+ .build();
+
+ mManager.addDynamicShortcuts(list(sorig, sorig2));
+
+ // Dynamic shortcuts won't be backed up, so we need to pin it.
+ setCaller(LAUNCHER_1, USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("id", "id2"), HANDLE_USER_0);
+
+ // Do backup & restore.
+ backupAndRestore();
+
+ mService.handleUnlockUser(USER_0); // Load user-0.
+
+ ShortcutInfo si;
+ si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_0);
+
+ assertEquals(CALLING_PACKAGE_1, si.getPackage());
+ assertEquals("id", si.getId());
+ 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(0, si.getRank());
+ assertEquals(1, si.getExtras().getInt("k"));
+
+ assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_STRINGS_RESOLVED
+ | ShortcutInfo.FLAG_SHADOW , si.getFlags());
+ assertNull(si.getBitmapPath()); // No icon.
+ assertEquals(0, si.getIconResourceId());
+ assertEquals(null, si.getIconResName());
+ assertNull(si.getIconUri());
// Note when restored from backup, it's no longer dynamic, so shouldn't have a rank.
si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_0);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index 8e78047..010f8ac 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -390,7 +390,7 @@
"title", 0, "titleResName", "text", 0, "textResName",
"disabledMessage", 0, "disabledMessageResName",
null, null, 0, null, 0, 0,
- 0, "iconResName", "bitmapPath", 0,
+ 0, "iconResName", "bitmapPath", null, 0,
null, null);
return si;
}