Merge "ShortcutManager: implement backup & restore" into nyc-dev
diff --git a/core/java/com/android/server/backup/ShortcutBackupHelper.java b/core/java/com/android/server/backup/ShortcutBackupHelper.java
new file mode 100644
index 0000000..0b3f2ae
--- /dev/null
+++ b/core/java/com/android/server/backup/ShortcutBackupHelper.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.backup;
+
+import android.app.backup.BlobBackupHelper;
+import android.content.Context;
+import android.content.pm.IShortcutService;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Slog;
+
+public class ShortcutBackupHelper extends BlobBackupHelper {
+ private static final String TAG = "ShortcutBackupAgent";
+ private static final int BLOB_VERSION = 1;
+
+ private static final String KEY_USER_FILE = "shortcutuser.xml";
+
+ public ShortcutBackupHelper() {
+ super(BLOB_VERSION, KEY_USER_FILE);
+ }
+
+ private IShortcutService getShortcutService() {
+ return IShortcutService.Stub.asInterface(
+ ServiceManager.getService(Context.SHORTCUT_SERVICE));
+ }
+
+ @Override
+ protected byte[] getBackupPayload(String key) {
+ switch (key) {
+ case KEY_USER_FILE:
+ try {
+ return getShortcutService().getBackupPayload(UserHandle.USER_SYSTEM);
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Backup failed", e);
+ }
+ break;
+ default:
+ Slog.w(TAG, "Unknown key: " + key);
+ }
+ return null;
+ }
+
+ @Override
+ protected void applyRestoredPayload(String key, byte[] payload) {
+ switch (key) {
+ case KEY_USER_FILE:
+ try {
+ getShortcutService().applyRestore(payload, UserHandle.USER_SYSTEM);
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Restore failed", e);
+ }
+ break;
+ default:
+ Slog.w(TAG, "Unknown key: " + key);
+ }
+ }
+}
diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java
index 181ed51..2d12fcd 100644
--- a/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -17,9 +17,9 @@
package com.android.server.backup;
import android.app.IWallpaperManager;
+import android.app.backup.BackupAgentHelper;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
-import android.app.backup.BackupAgentHelper;
import android.app.backup.FullBackup;
import android.app.backup.FullBackupDataOutput;
import android.app.backup.WallpaperBackupHelper;
@@ -48,6 +48,7 @@
private static final String NOTIFICATION_HELPER = "notifications";
private static final String PERMISSION_HELPER = "permissions";
private static final String USAGE_STATS_HELPER = "usage_stats";
+ private static final String SHORTCUT_MANAGER_HELPER = "shortcut_manager";
// These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME
// are also used in the full-backup file format, so must not change unless steps are
@@ -100,6 +101,7 @@
addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(this));
addHelper(PERMISSION_HELPER, new PermissionBackupHelper());
addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this));
+ addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
super.onBackup(oldState, data, newState);
}
@@ -138,6 +140,7 @@
addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(this));
addHelper(PERMISSION_HELPER, new PermissionBackupHelper());
addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this));
+ addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
try {
super.onRestore(data, appVersionCode, newState);
diff --git a/services/core/java/com/android/server/pm/ShortcutBackupAgent.java b/services/core/java/com/android/server/pm/ShortcutBackupAgent.java
deleted file mode 100644
index a0fbc37..0000000
--- a/services/core/java/com/android/server/pm/ShortcutBackupAgent.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.pm;
-
-import android.app.backup.BlobBackupHelper;
-
-public class ShortcutBackupAgent extends BlobBackupHelper {
- private static final String TAG = "ShortcutBackupAgent";
- private static final int BLOB_VERSION = 1;
-
- public ShortcutBackupAgent(int currentBlobVersion, String... keys) {
- super(currentBlobVersion, keys);
- }
-
- @Override
- protected byte[] getBackupPayload(String key) {
- throw new RuntimeException("not implemented yet"); // todo
- }
-
- @Override
- protected void applyRestoredPayload(String key, byte[] payload) {
- throw new RuntimeException("not implemented yet"); // todo
- }
-}
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index 7699f30..c6d66fe 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -20,6 +20,10 @@
import android.content.pm.ShortcutInfo;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.ShortcutUser.PackageWithUser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -27,6 +31,7 @@
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -43,15 +48,16 @@
private static final String ATTR_LAUNCHER_USER_ID = "launcher-user";
private static final String ATTR_VALUE = "value";
private static final String ATTR_PACKAGE_NAME = "package-name";
+ private static final String ATTR_PACKAGE_USER_ID = "package-user";
private final int mOwnerUserId;
/**
* Package name -> IDs.
*/
- final private ArrayMap<String, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
+ final private ArrayMap<PackageWithUser, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
- public ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName,
+ private ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName,
@UserIdInt int launcherUserId, ShortcutPackageInfo spi) {
super(launcherUserId, packageName, spi != null ? spi : ShortcutPackageInfo.newEmpty());
mOwnerUserId = ownerUserId;
@@ -59,7 +65,7 @@
public ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName,
@UserIdInt int launcherUserId) {
- this(launcherUserId, packageName, launcherUserId, null);
+ this(ownerUserId, packageName, launcherUserId, null);
}
@Override
@@ -67,16 +73,38 @@
return mOwnerUserId;
}
+ /**
+ * Called when the new package can't receive the backup, due to signature or version mismatch.
+ */
+ @Override
+ protected void onRestoreBlocked(ShortcutService s) {
+ final ArrayList<PackageWithUser> pinnedPackages =
+ new ArrayList<>(mPinnedShortcuts.keySet());
+ mPinnedShortcuts.clear();
+ for (int i = pinnedPackages.size() - 1; i >= 0; i--) {
+ final PackageWithUser pu = pinnedPackages.get(i);
+ s.getPackageShortcutsLocked(pu.packageName, pu.userId)
+ .refreshPinnedFlags(s);
+ }
+ }
+
+ @Override
+ protected void onRestored(ShortcutService s) {
+ // Nothing to do.
+ }
+
public void pinShortcuts(@NonNull ShortcutService s, @UserIdInt int packageUserId,
@NonNull String packageName, @NonNull List<String> ids) {
final ShortcutPackage packageShortcuts =
s.getPackageShortcutsLocked(packageName, packageUserId);
+ final PackageWithUser pu = PackageWithUser.of(packageUserId, packageName);
+
final int idSize = ids.size();
if (idSize == 0) {
- mPinnedShortcuts.remove(packageName);
+ mPinnedShortcuts.remove(pu);
} else {
- final ArraySet<String> prevSet = mPinnedShortcuts.get(packageName);
+ final ArraySet<String> prevSet = mPinnedShortcuts.get(pu);
// Pin shortcuts. Make sure only pin the ones that were visible to the caller.
// i.e. a non-dynamic, pinned shortcut by *other launchers* shouldn't be pinned here.
@@ -93,7 +121,7 @@
newSet.add(id);
}
}
- mPinnedShortcuts.put(packageName, newSet);
+ mPinnedShortcuts.put(pu, newSet);
}
packageShortcuts.refreshPinnedFlags(s);
}
@@ -101,12 +129,13 @@
/**
* Return the pinned shortcut IDs for the publisher package.
*/
- public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName) {
- return mPinnedShortcuts.get(packageName);
+ public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName,
+ @UserIdInt int packageUserId) {
+ return mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName));
}
- boolean cleanUpPackage(String packageName) {
- return mPinnedShortcuts.remove(packageName) != null;
+ boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) {
+ return mPinnedShortcuts.remove(PackageWithUser.of(packageUserId, packageName)) != null;
}
/**
@@ -126,9 +155,15 @@
getPackageInfo().saveToXml(out);
for (int i = 0; i < size; i++) {
+ final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
+
+ if (forBackup && (pu.userId != getOwnerUserId())) {
+ continue; // Target package on a different user, skip. (i.e. work profile)
+ }
+
out.startTag(null, TAG_PACKAGE);
- ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME,
- mPinnedShortcuts.keyAt(i));
+ ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, pu.packageName);
+ ShortcutService.writeAttr(out, ATTR_PACKAGE_USER_ID, pu.userId);
final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
final int idSize = ids.size();
@@ -157,8 +192,6 @@
final ShortcutLauncher ret = new ShortcutLauncher(launcherUserId, launcherPackageName,
launcherUserId);
- ShortcutPackageInfo spi = null;
-
ArraySet<String> ids = null;
final int outerDepth = parser.getDepth();
int type;
@@ -172,13 +205,17 @@
if (depth == outerDepth + 1) {
switch (tag) {
case ShortcutPackageInfo.TAG_ROOT:
- spi = ShortcutPackageInfo.loadFromXml(parser);
+ ret.getPackageInfo().loadFromXml(parser, fromBackup);
continue;
case TAG_PACKAGE: {
final String packageName = ShortcutService.parseStringAttribute(parser,
ATTR_PACKAGE_NAME);
+ final int packageUserId = fromBackup ? ownerUserId
+ : ShortcutService.parseIntAttribute(parser,
+ ATTR_PACKAGE_USER_ID, ownerUserId);
ids = new ArraySet<>();
- ret.mPinnedShortcuts.put(packageName, ids);
+ ret.mPinnedShortcuts.put(
+ PackageWithUser.of(packageUserId, packageName), ids);
continue;
}
}
@@ -186,17 +223,17 @@
if (depth == outerDepth + 2) {
switch (tag) {
case TAG_PIN: {
- ids.add(ShortcutService.parseStringAttribute(parser,
- ATTR_VALUE));
+ if (ids == null) {
+ Slog.w(TAG, TAG_PIN + " in invalid place");
+ } else {
+ ids.add(ShortcutService.parseStringAttribute(parser, ATTR_VALUE));
+ }
continue;
}
}
}
ShortcutService.warnForInvalidTag(depth, tag);
}
- if (spi != null) {
- ret.replacePackageInfo(spi);
- }
return ret;
}
@@ -208,6 +245,8 @@
pw.print(getPackageName());
pw.print(" Package user: ");
pw.print(getPackageUserId());
+ pw.print(" Owner user: ");
+ pw.print(getOwnerUserId());
pw.println();
getPackageInfo().dump(s, pw, prefix + " ");
@@ -217,10 +256,14 @@
for (int i = 0; i < size; i++) {
pw.println();
+ final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
+
pw.print(prefix);
pw.print(" ");
pw.print("Package: ");
- pw.println(mPinnedShortcuts.keyAt(i));
+ pw.print(pu.packageName);
+ pw.print(" User: ");
+ pw.println(pu.userId);
final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
final int idSize = ids.size();
@@ -233,4 +276,9 @@
}
}
}
+
+ @VisibleForTesting
+ ArraySet<String> getAllPinnedShortcutsForTest(String packageName, int packageUserId) {
+ return new ArraySet<>(mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName)));
+ }
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 5916202..1076a7a 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -26,6 +26,8 @@
import android.util.ArraySet;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -34,6 +36,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
@@ -83,7 +86,7 @@
*/
private long mLastResetTime;
- public ShortcutPackage(int packageUserId, String packageName, ShortcutPackageInfo spi) {
+ private ShortcutPackage(int packageUserId, String packageName, ShortcutPackageInfo spi) {
super(packageUserId, packageName, spi != null ? spi : ShortcutPackageInfo.newEmpty());
}
@@ -97,6 +100,19 @@
return getPackageUserId();
}
+ @Override
+ protected void onRestoreBlocked(ShortcutService s) {
+ // Can't restore due to version/signature mismatch. Remove all shortcuts.
+ mShortcuts.clear();
+ }
+
+ @Override
+ protected void onRestored(ShortcutService s) {
+ // Because some launchers may not have been restored (e.g. allowBackup=false),
+ // we need to re-calculate the pinned shortcuts.
+ refreshPinnedFlags(s);
+ }
+
/**
* Note this does *not* provide a correct view to the calling launcher.
*/
@@ -229,20 +245,26 @@
s.getUserShortcutsLocked(getPackageUserId()).getAllLaunchers();
for (int l = launchers.size() - 1; l >= 0; l--) {
+ // Note even if a launcher that hasn't been installed can still pin shortcuts.
+
final ShortcutLauncher launcherShortcuts = launchers.valueAt(l);
final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(
- getPackageName());
+ getPackageName(), getPackageUserId());
if (pinned == null || pinned.size() == 0) {
continue;
}
for (int i = pinned.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.get(pinned.valueAt(i));
+ final String id = pinned.valueAt(i);
+ final ShortcutInfo si = mShortcuts.get(id);
if (si == null) {
- s.wtf("Shortcut not found");
- } else {
- si.addFlags(ShortcutInfo.FLAG_PINNED);
+ // This happens if a launcher pinned shortcuts from this package, then backup&
+ // restored, but this package doesn't allow backing up.
+ // In that case the launcher ends up having a dangling pinned shortcuts.
+ // That's fine, when the launcher is restored, we'll fix it.
+ continue;
}
+ si.addFlags(ShortcutInfo.FLAG_PINNED);
}
}
@@ -312,11 +334,15 @@
public void findAll(@NonNull ShortcutService s, @NonNull List<ShortcutInfo> result,
@Nullable Predicate<ShortcutInfo> query, int cloneFlag,
@Nullable String callingLauncher, int launcherUserId) {
+ if (getPackageInfo().isShadow()) {
+ // Restored and the app not installed yet, so don't return any.
+ return;
+ }
// Set of pinned shortcuts by the calling launcher.
final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
- : s.getLauncherShortcuts(callingLauncher, getPackageUserId(), launcherUserId)
- .getPinnedShortcutIds(getPackageName());
+ : s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId)
+ .getPinnedShortcutIds(getPackageName(), getPackageUserId());
for (int i = 0; i < mShortcuts.size(); i++) {
final ShortcutInfo si = mShortcuts.valueAt(i);
@@ -328,7 +354,8 @@
|| ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId()));
if (!si.isDynamic()) {
if (!si.isPinned()) {
- s.wtf("Shortcut not pinned here");
+ s.wtf("Shortcut not pinned: package " + getPackageName()
+ + ", user=" + getPackageUserId() + ", id=" + si.getId());
continue;
}
if (!isPinnedByCaller) {
@@ -479,7 +506,6 @@
ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
ret.mLastResetTime =
ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET);
- ShortcutPackageInfo spi = null;
final int outerDepth = parser.getDepth();
int type;
@@ -493,7 +519,7 @@
if (depth == outerDepth + 1) {
switch (tag) {
case ShortcutPackageInfo.TAG_ROOT:
- spi = ShortcutPackageInfo.loadFromXml(parser);
+ ret.getPackageInfo().loadFromXml(parser, fromBackup);
continue;
case TAG_SHORTCUT:
final ShortcutInfo si = parseShortcut(parser, packageName);
@@ -505,9 +531,6 @@
}
ShortcutService.warnForInvalidTag(depth, tag);
}
- if (spi != null) {
- ret.replacePackageInfo(spi);
- }
return ret;
}
@@ -567,4 +590,9 @@
intentPersistableExtras, weight, extras, lastChangedTimestamp, flags,
iconRes, bitmapPath);
}
+
+ @VisibleForTesting
+ List<ShortcutInfo> getAllShortcutsForTest() {
+ return new ArrayList<>(mShortcuts.values());
+ }
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
index 5f706b8..2c45890 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
@@ -67,10 +67,6 @@
return mIsShadow;
}
- public boolean isInstalled() {
- return !mIsShadow;
- }
-
public void setShadow(boolean shadow) {
mIsShadow = shadow;
}
@@ -79,14 +75,24 @@
return mVersionCode;
}
- public boolean canRestoreTo(PackageInfo target) {
+ public boolean hasSignatures() {
+ return mSigHashes.size() > 0;
+ }
+
+ public boolean canRestoreTo(ShortcutService s, PackageInfo target) {
+ if (!s.shouldBackupApp(target)) {
+ // "allowBackup" was true when backed up, but now false.
+ Slog.w(TAG, "Can't restore: package no longer allows backup");
+ return false;
+ }
if (target.versionCode < mVersionCode) {
- Slog.w(TAG, String.format("Package current version %d < backed up version %d",
+ Slog.w(TAG, String.format(
+ "Can't restore: package current version %d < backed up version %d",
target.versionCode, mVersionCode));
return false;
}
if (!BackupUtils.signaturesMatch(mSigHashes, target)) {
- Slog.w(TAG, "Package signature mismtach");
+ Slog.w(TAG, "Can't restore: Package signature mismatch");
return false;
}
return true;
@@ -106,6 +112,11 @@
}
public void refresh(ShortcutService s, ShortcutPackageItem pkg) {
+ if (mIsShadow) {
+ s.wtf("Attempted to refresh package info for shadow package " + pkg.getPackageName()
+ + ", user=" + pkg.getOwnerUserId());
+ return;
+ }
// Note use mUserId here, rather than userId.
final PackageInfo pi = s.getPackageInfoWithSignatures(
pkg.getPackageName(), pkg.getPackageUserId());
@@ -132,15 +143,17 @@
out.endTag(null, TAG_ROOT);
}
- public static ShortcutPackageInfo loadFromXml(XmlPullParser parser)
+ public void loadFromXml(XmlPullParser parser, boolean fromBackup)
throws IOException, XmlPullParserException {
final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION);
- final boolean shadow = ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
+
+ // When restoring from backup, it's always shadow.
+ final boolean shadow =
+ fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
final ArrayList<byte[]> hashes = new ArrayList<>();
-
final int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -163,7 +176,11 @@
}
ShortcutService.warnForInvalidTag(depth, tag);
}
- return new ShortcutPackageInfo(versionCode, hashes, shadow);
+
+ // Successfully loaded; replace the feilds.
+ mVersionCode = versionCode;
+ mIsShadow = shadow;
+ mSigHashes = hashes;
}
public void dump(ShortcutService s, PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index de2709d..f31dd17 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -16,6 +16,8 @@
package com.android.server.pm;
import android.annotation.NonNull;
+import android.content.pm.PackageInfo;
+import android.util.Slog;
import com.android.internal.util.Preconditions;
@@ -25,10 +27,12 @@
import java.io.IOException;
abstract class ShortcutPackageItem {
+ private static final String TAG = ShortcutService.TAG;
+
private final int mPackageUserId;
private final String mPackageName;
- private ShortcutPackageInfo mPackageInfo;
+ private final ShortcutPackageInfo mPackageInfo;
protected ShortcutPackageItem(int packageUserId, @NonNull String packageName,
@NonNull ShortcutPackageInfo packageInfo) {
@@ -61,25 +65,62 @@
return mPackageInfo;
}
- /**
- * Should be only used when loading from a file.o
- */
- protected void replacePackageInfo(@NonNull ShortcutPackageInfo packageInfo) {
- mPackageInfo = Preconditions.checkNotNull(packageInfo);
- }
-
public void refreshPackageInfoAndSave(ShortcutService s) {
+ if (mPackageInfo.isShadow()) {
+ return; // Don't refresh for shadow user.
+ }
mPackageInfo.refresh(s, this);
s.scheduleSaveUser(getOwnerUserId());
}
- public void ensureNotShadowAndSave(ShortcutService s) {
- if (mPackageInfo.isShadow()) {
- mPackageInfo.setShadow(false);
- s.scheduleSaveUser(getOwnerUserId());
+ public void attemptToRestoreIfNeededAndSave(ShortcutService s) {
+ if (!mPackageInfo.isShadow()) {
+ return; // Already installed, nothing to do.
}
+ if (!s.isPackageInstalled(mPackageName, mPackageUserId)) {
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, String.format("Package still not installed: %s user=%d",
+ mPackageName, mPackageUserId));
+ }
+ return; // Not installed, no need to restore yet.
+ }
+ if (!mPackageInfo.hasSignatures()) {
+ s.wtf("Attempted to restore package " + mPackageName + ", user=" + mPackageUserId
+ + " but signatures not found in the restore data.");
+ onRestoreBlocked(s);
+ return;
+ }
+
+ final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, mPackageUserId);
+ if (!mPackageInfo.canRestoreTo(s, pi)) {
+ // Package is now installed, but can't restore. Let the subclass do the cleanup.
+ onRestoreBlocked(s);
+ return;
+ }
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, String.format("Restored package: %s/%d on user %d", mPackageName,
+ mPackageUserId, getOwnerUserId()));
+ }
+
+ onRestored(s);
+
+ // Now the package is not shadow.
+ mPackageInfo.setShadow(false);
+
+ s.scheduleSaveUser(mPackageUserId);
}
+ /**
+ * Called when the new package can't be restored because it has a lower version number
+ * or different signatures.
+ */
+ protected abstract void onRestoreBlocked(ShortcutService s);
+
+ /**
+ * Called when the new package is successfully restored.
+ */
+ protected abstract void onRestored(ShortcutService s);
+
public abstract void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException;
}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 76a2dfa..7aefcb8 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -46,6 +46,7 @@
import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
@@ -101,6 +102,7 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
import java.util.function.Predicate;
/**
@@ -112,17 +114,16 @@
*
* - Scan and remove orphan bitmaps (just in case).
*
- * - Backup & restore
- *
* - Detect when already registered instances are passed to APIs again, which might break
* internal bitmap handling.
+ *
+ * - Add more call stats.
*/
public class ShortcutService extends IShortcutService.Stub {
static final String TAG = "ShortcutService";
static final boolean DEBUG = false; // STOPSHIP if true
static final boolean DEBUG_LOAD = false; // STOPSHIP if true
- static final boolean ENABLE_DEBUG_COMMAND = true; // STOPSHIP if true
@VisibleForTesting
static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
@@ -262,6 +263,26 @@
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_UNINSTALLED_PACKAGES;
+ // Stats
+ @VisibleForTesting
+ interface Stats {
+ int GET_DEFAULT_HOME = 0;
+ int GET_PACKAGE_INFO = 1;
+ int GET_PACKAGE_INFO_WITH_SIG = 2;
+ int GET_APPLICATION_INFO = 3;
+ int LAUNCHER_PERMISSION_CHECK = 4;
+
+ int COUNT = LAUNCHER_PERMISSION_CHECK + 1;
+ }
+
+ final Object mStatLock = new Object();
+
+ @GuardedBy("mStatLock")
+ private final int[] mCountStats = new int[Stats.COUNT];
+
+ @GuardedBy("mStatLock")
+ private final long[] mDurationStats = new long[Stats.COUNT];
+
public ShortcutService(Context context) {
this(context, BackgroundThread.get().getLooper());
}
@@ -278,6 +299,13 @@
mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false);
}
+ void logDurationStat(int statId, long start) {
+ synchronized (mStatLock) {
+ mCountStats[statId]++;
+ mDurationStats[statId] += (System.currentTimeMillis() - start);
+ }
+ }
+
/**
* System service lifecycle.
*/
@@ -822,7 +850,7 @@
@GuardedBy("mLock")
@NonNull
- boolean isUserLoadedLocked(@UserIdInt int userId) {
+ private boolean isUserLoadedLocked(@UserIdInt int userId) {
return mUsers.get(userId) != null;
}
@@ -841,19 +869,27 @@
return userPackages;
}
+ void forEachLoadedUserLocked(@NonNull Consumer<ShortcutUser> c) {
+ for (int i = mUsers.size() - 1; i >= 0; i--) {
+ c.accept(mUsers.valueAt(i));
+ }
+ }
+
/** Return the per-user per-package state. */
@GuardedBy("mLock")
@NonNull
ShortcutPackage getPackageShortcutsLocked(
@NonNull String packageName, @UserIdInt int userId) {
- return getUserShortcutsLocked(userId).getPackageShortcuts(packageName);
+ return getUserShortcutsLocked(userId).getPackageShortcuts(this, packageName);
}
@GuardedBy("mLock")
@NonNull
- ShortcutLauncher getLauncherShortcuts(
- @NonNull String packageName, @UserIdInt int userId, @UserIdInt int launcherUserId) {
- return getUserShortcutsLocked(userId).getLauncherShortcuts(packageName, launcherUserId);
+ ShortcutLauncher getLauncherShortcutsLocked(
+ @NonNull String packageName, @UserIdInt int ownerUserId,
+ @UserIdInt int launcherUserId) {
+ return getUserShortcutsLocked(ownerUserId)
+ .getLauncherShortcuts(this, packageName, launcherUserId);
}
// === Caller validation ===
@@ -1212,8 +1248,6 @@
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
- ps.ensureNotShadowAndSave(this);
-
// Throttling.
if (!ps.tryApiCall(this)) {
return false;
@@ -1249,8 +1283,6 @@
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
- ps.ensureNotShadowAndSave(this);
-
// Throttling.
if (!ps.tryApiCall(this)) {
return false;
@@ -1288,8 +1320,6 @@
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
- ps.ensureNotShadowAndSave(this);
-
// Throttling.
if (!ps.tryApiCall(this)) {
return false;
@@ -1422,15 +1452,17 @@
@VisibleForTesting
boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) {
synchronized (mLock) {
- long start = System.currentTimeMillis();
+ final long start = System.currentTimeMillis();
final ShortcutUser user = getUserShortcutsLocked(userId);
final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
// Default launcher from package manager.
+ final long startGetHomeActivitiesAsUser = System.currentTimeMillis();
final ComponentName defaultLauncher = injectPackageManagerInternal()
.getHomeActivitiesAsUser(allHomeCandidates, userId);
+ logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeActivitiesAsUser);
ComponentName detected;
if (defaultLauncher != null) {
@@ -1473,10 +1505,8 @@
lastPriority = ri.priority;
}
}
- final long end = System.currentTimeMillis();
- if (DEBUG) {
- Slog.v(TAG, String.format("hasShortcutPermission took %d ms", end - start));
- }
+ logDurationStat(Stats.LAUNCHER_PERMISSION_CHECK, start);
+
if (detected != null) {
if (DEBUG) {
Slog.v(TAG, "Detected launcher: " + detected);
@@ -1492,10 +1522,17 @@
// === House keeping ===
+ /**
+ * Remove all the information associated with a package. This will really remove all the
+ * information, including the restore information (i.e. it'll remove packages even if they're
+ * shadow).
+ */
@VisibleForTesting
void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId) {
-
- // TODO Don't remove shadow packages' information.
+ if (isPackageInstalled(packageName, packageUserId)) {
+ wtf("Package " + packageName + " is still installed for user " + packageUserId);
+ return;
+ }
final boolean wasUserLoaded = isUserLoadedLocked(owningUserId);
@@ -1504,7 +1541,7 @@
// First, remove the package from the package list (if the package is a publisher).
if (packageUserId == owningUserId) {
- if (mUser.getPackages().remove(packageName) != null) {
+ if (mUser.removePackage(packageName) != null) {
doNotify = true;
}
}
@@ -1515,12 +1552,12 @@
// Then remove pinned shortcuts from all launchers.
final ArrayMap<PackageWithUser, ShortcutLauncher> launchers = mUser.getAllLaunchers();
for (int i = launchers.size() - 1; i >= 0; i--) {
- launchers.valueAt(i).cleanUpPackage(packageName);
+ launchers.valueAt(i).cleanUpPackage(packageName, packageUserId);
}
// Now there may be orphan shortcuts because we removed pinned shortucts at the previous
// step. Remove them too.
- for (int i = mUser.getPackages().size() - 1; i >= 0; i--) {
- mUser.getPackages().valueAt(i).refreshPinnedFlags(this);
+ for (int i = mUser.getAllPackages().size() - 1; i >= 0; i--) {
+ mUser.getAllPackages().valueAt(i).refreshPinnedFlags(this);
}
scheduleSaveUser(owningUserId);
@@ -1539,6 +1576,7 @@
* Entry point from {@link LauncherApps}.
*/
private class LocalService extends ShortcutServiceInternal {
+
@Override
public List<ShortcutInfo> getShortcuts(int launcherUserId,
@NonNull String callingPackage, long changedSince,
@@ -1551,13 +1589,16 @@
: ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO;
synchronized (mLock) {
+ getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+ .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+
if (packageName != null) {
getShortcutsInnerLocked(launcherUserId,
callingPackage, packageName, changedSince,
componentName, queryFlags, userId, ret, cloneFlag);
} else {
final ArrayMap<String, ShortcutPackage> packages =
- getUserShortcutsLocked(userId).getPackages();
+ getUserShortcutsLocked(userId).getAllPackages();
for (int i = packages.size() - 1; i >= 0; i--) {
getShortcutsInnerLocked(launcherUserId,
callingPackage, packages.keyAt(i), changedSince,
@@ -1601,6 +1642,9 @@
final ArrayList<ShortcutInfo> ret = new ArrayList<>(ids.size());
final ArraySet<String> idSet = new ArraySet<>(ids);
synchronized (mLock) {
+ getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+ .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+
getPackageShortcutsLocked(packageName, userId).findAll(
ShortcutService.this, ret,
(ShortcutInfo si) -> idSet.contains(si.getId()),
@@ -1616,13 +1660,16 @@
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
synchronized (mLock) {
+ getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+ .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+
final ShortcutInfo si = getShortcutInfoLocked(
launcherUserId, callingPackage, packageName, shortcutId, userId);
return si != null && si.isPinned();
}
}
- public ShortcutInfo getShortcutInfoLocked(
+ private ShortcutInfo getShortcutInfoLocked(
int launcherUserId, @NonNull String callingPackage,
@NonNull String packageName, @NonNull String shortcutId, int userId) {
Preconditions.checkStringNotEmpty(packageName, "packageName");
@@ -1646,9 +1693,8 @@
synchronized (mLock) {
final ShortcutLauncher launcher =
- getLauncherShortcuts(callingPackage, userId, launcherUserId);
-
- launcher.ensureNotShadowAndSave(ShortcutService.this);
+ getLauncherShortcutsLocked(callingPackage, userId, launcherUserId);
+ launcher.attemptToRestoreIfNeededAndSave(ShortcutService.this);
launcher.pinShortcuts(
ShortcutService.this, userId, packageName, shortcutIds);
@@ -1665,6 +1711,9 @@
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
synchronized (mLock) {
+ getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+ .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+
// Make sure the shortcut is actually visible to the launcher.
final ShortcutInfo si = getShortcutInfoLocked(
launcherUserId, callingPackage, packageName, shortcutId, userId);
@@ -1690,6 +1739,9 @@
Preconditions.checkNotNull(shortcut, "shortcut");
synchronized (mLock) {
+ getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+ .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+
final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
shortcut.getPackageName(), userId).findShortcutById(shortcut.getId());
return (shortcutInfo != null && shortcutInfo.hasIconResource())
@@ -1704,6 +1756,9 @@
Preconditions.checkNotNull(shortcutIn, "shortcut");
synchronized (mLock) {
+ getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+ .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+
final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
shortcutIn.getPackageName(), userId).findShortcutById(shortcutIn.getId());
if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
@@ -1757,28 +1812,28 @@
* perform cleanup.
*/
@VisibleForTesting
- void cleanupGonePackages(@UserIdInt int userId) {
+ void cleanupGonePackages(@UserIdInt int ownerUserId) {
if (DEBUG) {
- Slog.d(TAG, "cleanupGonePackages() userId=" + userId);
+ Slog.d(TAG, "cleanupGonePackages() ownerUserId=" + ownerUserId);
}
final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
synchronized (mLock) {
- final ShortcutUser user = getUserShortcutsLocked(userId);
+ final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
user.forAllPackageItems(spi -> {
if (spi.getPackageInfo().isShadow()) {
return; // Don't delete shadow information.
}
if (isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) {
- return;
+ return; // Package not gone.
}
gonePackages.add(PackageWithUser.of(spi));
});
if (gonePackages.size() > 0) {
for (int i = gonePackages.size() - 1; i >= 0; i--) {
final PackageWithUser pu = gonePackages.get(i);
- cleanUpPackageLocked(pu.packageName, userId, pu.userId);
+ cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId);
}
}
}
@@ -1789,7 +1844,8 @@
Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
}
synchronized (mLock) {
- getUserShortcutsLocked(userId).unshadowPackage(this, packageName, userId);
+ forEachLoadedUserLocked(user ->
+ user.attemptToRestoreIfNeededAndSave(this, packageName, userId));
}
}
@@ -1798,18 +1854,20 @@
Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d",
packageName, userId));
}
-
synchronized (mLock) {
- getUserShortcutsLocked(userId).unshadowPackage(this, packageName, userId);
+ forEachLoadedUserLocked(user ->
+ user.attemptToRestoreIfNeededAndSave(this, packageName, userId));
}
}
- private void handlePackageRemoved(String packageName, @UserIdInt int userId) {
+ private void handlePackageRemoved(String packageName, @UserIdInt int packageUserId) {
if (DEBUG) {
- Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName, userId));
+ Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName,
+ packageUserId));
}
synchronized (mLock) {
- cleanUpPackageLocked(packageName, userId, userId);
+ forEachLoadedUserLocked(user ->
+ cleanUpPackageLocked(packageName, user.getUserId(), packageUserId));
}
}
@@ -1836,6 +1894,7 @@
@VisibleForTesting
PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
boolean getSignatures) {
+ final long start = System.currentTimeMillis();
final long token = injectClearCallingIdentity();
try {
return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS
@@ -1847,11 +1906,16 @@
return null;
} finally {
injectRestoreCallingIdentity(token);
+
+ logDurationStat(
+ (getSignatures ? Stats.GET_PACKAGE_INFO_WITH_SIG : Stats.GET_PACKAGE_INFO),
+ start);
}
}
@VisibleForTesting
ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
+ final long start = System.currentTimeMillis();
final long token = injectClearCallingIdentity();
try {
return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId);
@@ -1861,6 +1925,8 @@
return null;
} finally {
injectRestoreCallingIdentity(token);
+
+ logDurationStat(Stats.GET_APPLICATION_INFO, start);
}
}
@@ -1869,7 +1935,7 @@
return (ai != null) && ((ai.flags & flags) == flags);
}
- private boolean isPackageInstalled(String packageName, int userId) {
+ boolean isPackageInstalled(String packageName, int userId) {
return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED);
}
@@ -1879,8 +1945,12 @@
return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP);
}
+ boolean shouldBackupApp(PackageInfo pi) {
+ return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
+ }
+
@Override
- public byte[] getBackupPayload(@UserIdInt int userId) throws RemoteException {
+ public byte[] getBackupPayload(@UserIdInt int userId) {
enforceSystem();
if (DEBUG) {
Slog.d(TAG, "Backing up user " + userId);
@@ -1908,7 +1978,7 @@
}
@Override
- public void applyRestore(byte[] payload, @UserIdInt int userId) throws RemoteException {
+ public void applyRestore(byte[] payload, @UserIdInt int userId) {
enforceSystem();
if (DEBUG) {
Slog.d(TAG, "Restoring user " + userId);
@@ -1923,6 +1993,15 @@
}
synchronized (mLock) {
mUsers.put(userId, user);
+
+ // Then purge all the save images.
+ final File bitmapPath = getUserBitmapFilePath(userId);
+ final boolean success = FileUtils.deleteContents(bitmapPath);
+ if (!success) {
+ Slog.w(TAG, "Failed to delete " + bitmapPath);
+ }
+
+ saveUserLocked(userId);
}
}
@@ -1974,9 +2053,19 @@
pw.print(" Icon format: ");
pw.print(mIconPersistFormat);
pw.print(" Icon quality: ");
- pw.print(mIconPersistQuality);
+ pw.println(mIconPersistQuality);
pw.println();
+ pw.println(" Stats:");
+ synchronized (mStatLock) {
+ final String p = " ";
+ dumpStatLS(pw, p, Stats.GET_DEFAULT_HOME, "getHomeActivities()");
+ dumpStatLS(pw, p, Stats.LAUNCHER_PERMISSION_CHECK, "Launcher permission check");
+
+ dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO, "getPackageInfo()");
+ dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO_WITH_SIG, "getPackageInfo(SIG)");
+ dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo");
+ }
for (int i = 0; i < mUsers.size(); i++) {
pw.println();
@@ -1991,6 +2080,15 @@
return tobj.format("%Y-%m-%d %H:%M:%S");
}
+ private void dumpStatLS(PrintWriter pw, String prefix, int statId, String label) {
+ pw.print(prefix);
+ final int count = mCountStats[statId];
+ final long dur = mDurationStats[statId];
+ pw.println(String.format("%s: count=%d, total=%dms, avg=%.1fms",
+ label, count, dur,
+ (count == 0 ? 0 : ((double) dur) / count)));
+ }
+
// === Shell support ===
@Override
@@ -2202,9 +2300,10 @@
}
final void wtf(String message) {
- Slog.wtf(TAG, message, /* exception= */ null);
+ wtf( message, /* exception= */ null);
}
+ // Injection point.
void wtf(String message, Exception e) {
Slog.wtf(TAG, message, e);
}
@@ -2275,7 +2374,7 @@
final ShortcutUser user = mUsers.get(userId);
if (user == null) return null;
- final ShortcutPackage pkg = user.getPackages().get(packageName);
+ final ShortcutPackage pkg = user.getAllPackages().get(packageName);
if (pkg == null) return null;
return pkg.findShortcutById(shortcutId);
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 487558f..593f607 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -53,8 +53,8 @@
this.packageName = Preconditions.checkNotNull(packageName);
}
- public static PackageWithUser of(int launcherUserId, String packageName) {
- return new PackageWithUser(launcherUserId, packageName);
+ public static PackageWithUser of(int userId, String packageName) {
+ return new PackageWithUser(userId, packageName);
}
public static PackageWithUser of(ShortcutPackageItem spi) {
@@ -78,12 +78,12 @@
@Override
public String toString() {
- return String.format("{Launcher: %d, %s}", userId, packageName);
+ return String.format("{Package: %d, %s}", userId, packageName);
}
}
@UserIdInt
- final int mUserId;
+ private final int mUserId;
private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>();
@@ -95,10 +95,18 @@
mUserId = userId;
}
- public ArrayMap<String, ShortcutPackage> getPackages() {
+ public int getUserId() {
+ return mUserId;
+ }
+
+ public ArrayMap<String, ShortcutPackage> getAllPackages() {
return mPackages;
}
+ public ShortcutPackage removePackage(@NonNull String packageName) {
+ return mPackages.remove(packageName);
+ }
+
public ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchers() {
return mLaunchers;
}
@@ -113,22 +121,26 @@
return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
}
- public ShortcutPackage getPackageShortcuts(@NonNull String packageName) {
+ public ShortcutPackage getPackageShortcuts(ShortcutService s, @NonNull String packageName) {
ShortcutPackage ret = mPackages.get(packageName);
if (ret == null) {
ret = new ShortcutPackage(mUserId, packageName);
mPackages.put(packageName, ret);
+ } else {
+ ret.attemptToRestoreIfNeededAndSave(s);
}
return ret;
}
- public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName,
+ public ShortcutLauncher getLauncherShortcuts(ShortcutService s, @NonNull String packageName,
@UserIdInt int launcherUserId) {
final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName);
ShortcutLauncher ret = mLaunchers.get(key);
if (ret == null) {
ret = new ShortcutLauncher(mUserId, packageName, launcherUserId);
mLaunchers.put(key, ret);
+ } else {
+ ret.attemptToRestoreIfNeededAndSave(s);
}
return ret;
}
@@ -148,14 +160,6 @@
}
}
- public void unshadowPackage(ShortcutService s, @NonNull String packageName,
- @UserIdInt int packageUserId) {
- forPackageItem(packageName, packageUserId, spi -> {
- Slog.i(TAG, String.format("Restoring for %s, user=%d", packageName, packageUserId));
- spi.ensureNotShadowAndSave(s);
- });
- }
-
public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId,
Consumer<ShortcutPackageItem> callback) {
forAllPackageItems(spi -> {
@@ -166,6 +170,13 @@
});
}
+ public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,
+ @UserIdInt int packageUserId) {
+ forPackageItem(packageName, packageUserId, spi -> {
+ spi.attemptToRestoreIfNeededAndSave(s);
+ });
+ }
+
public void saveToXml(ShortcutService s, XmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException {
out.startTag(null, TAG_ROOT);
@@ -229,7 +240,7 @@
s, parser, userId, fromBackup);
// Don't use addShortcut(), we don't need to save the icon.
- ret.getPackages().put(shortcuts.getPackageName(), shortcuts);
+ ret.mPackages.put(shortcuts.getPackageName(), shortcuts);
continue;
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java
deleted file mode 100644
index c44ffa4..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java
+++ /dev/null
@@ -1,271 +0,0 @@
-
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.pm;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.ShortcutInfo;
-import android.graphics.drawable.Icon;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.PersistableBundle;
-import android.test.AndroidTestCase;
-
-import com.android.internal.util.Preconditions;
-import com.android.server.testutis.TestUtils;
-
-/**
- * Tests for {@link ShortcutInfo}.
-
- m FrameworksServicesTests &&
- adb install \
- -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
- adb shell am instrument -e class com.android.server.pm.ShortcutInfoTest \
- -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
-
- */
-public class ShortcutInfoTest extends AndroidTestCase {
-
- public void testMissingMandatoryFields() {
- TestUtils.assertExpectException(
- IllegalArgumentException.class,
- "ID must be provided",
- () -> new ShortcutInfo.Builder(mContext).build());
- TestUtils.assertExpectException(
- IllegalArgumentException.class,
- "title must be provided",
- () -> new ShortcutInfo.Builder(mContext).setId("id").build()
- .enforceMandatoryFields());
- TestUtils.assertExpectException(
- NullPointerException.class,
- "Intent must be provided",
- () -> new ShortcutInfo.Builder(mContext).setId("id").setTitle("x").build()
- .enforceMandatoryFields());
- }
-
- private ShortcutInfo parceled(ShortcutInfo si) {
- Parcel p = Parcel.obtain();
- p.writeParcelable(si, 0);
- p.setDataPosition(0);
- ShortcutInfo si2 = p.readParcelable(getClass().getClassLoader());
- p.recycle();
- return si2;
- }
-
- private Intent makeIntent(String action, Object... bundleKeysAndValues) {
- final Intent intent = new Intent(action);
- intent.replaceExtras(ShortcutManagerTest.makeBundle(bundleKeysAndValues));
- return intent;
- }
-
- public void testParcel() {
- ShortcutInfo si = parceled(new ShortcutInfo.Builder(getContext())
- .setId("id")
- .setTitle("title")
- .setIntent(makeIntent("action"))
- .build());
- assertEquals(getContext().getPackageName(), si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals("title", si.getTitle());
- assertEquals("action", si.getIntent().getAction());
-
- PersistableBundle pb = new PersistableBundle();
- pb.putInt("k", 1);
-
- si = new ShortcutInfo.Builder(getContext())
- .setId("id")
- .setActivityComponent(new ComponentName("a", "b"))
- .setIcon(Icon.createWithContentUri("content://a.b.c/"))
- .setTitle("title")
- .setText("text")
- .setIntent(makeIntent("action", "key", "val"))
- .setWeight(123)
- .setExtras(pb)
- .build();
- si.addFlags(ShortcutInfo.FLAG_PINNED);
- si.setBitmapPath("abc");
- si.setIconResourceId(456);
-
- si = parceled(si);
-
- assertEquals(getContext().getPackageName(), si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
- assertEquals("content://a.b.c/", si.getIcon().getUriString());
- assertEquals("title", si.getTitle());
- assertEquals("text", si.getText());
- assertEquals("action", si.getIntent().getAction());
- assertEquals("val", si.getIntent().getStringExtra("key"));
- assertEquals(123, si.getWeight());
- assertEquals(1, si.getExtras().getInt("k"));
-
- assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
- assertEquals("abc", si.getBitmapPath());
- assertEquals(456, si.getIconResourceId());
- }
-
- public void testClone() {
- PersistableBundle pb = new PersistableBundle();
- pb.putInt("k", 1);
- ShortcutInfo sorig = new ShortcutInfo.Builder(getContext())
- .setId("id")
- .setActivityComponent(new ComponentName("a", "b"))
- .setIcon(Icon.createWithContentUri("content://a.b.c/"))
- .setTitle("title")
- .setText("text")
- .setIntent(makeIntent("action", "key", "val"))
- .setWeight(123)
- .setExtras(pb)
- .build();
- sorig.addFlags(ShortcutInfo.FLAG_PINNED);
- sorig.setBitmapPath("abc");
- sorig.setIconResourceId(456);
-
- ShortcutInfo si = sorig.clone(/* clone flags*/ 0);
-
- assertEquals(getContext().getPackageName(), si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
- assertEquals("content://a.b.c/", si.getIcon().getUriString());
- assertEquals("title", si.getTitle());
- assertEquals("text", si.getText());
- assertEquals("action", si.getIntent().getAction());
- assertEquals("val", si.getIntent().getStringExtra("key"));
- assertEquals(123, si.getWeight());
- assertEquals(1, si.getExtras().getInt("k"));
-
- assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
- assertEquals("abc", si.getBitmapPath());
- assertEquals(456, si.getIconResourceId());
-
- si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
-
- assertEquals(getContext().getPackageName(), si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
- assertEquals(null, si.getIcon());
- assertEquals("title", si.getTitle());
- assertEquals("text", si.getText());
- assertEquals("action", si.getIntent().getAction());
- assertEquals("val", si.getIntent().getStringExtra("key"));
- assertEquals(123, si.getWeight());
- assertEquals(1, si.getExtras().getInt("k"));
-
- assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
- assertEquals(null, si.getBitmapPath());
- assertEquals(0, si.getIconResourceId());
-
- si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
-
- assertEquals(getContext().getPackageName(), si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
- assertEquals(null, si.getIcon());
- assertEquals("title", si.getTitle());
- assertEquals("text", si.getText());
- assertEquals(null, si.getIntent());
- assertEquals(123, si.getWeight());
- assertEquals(1, si.getExtras().getInt("k"));
-
- assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
- assertEquals(null, si.getBitmapPath());
- assertEquals(0, si.getIconResourceId());
-
- si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
-
- assertEquals(getContext().getPackageName(), si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals(null, si.getActivityComponent());
- assertEquals(null, si.getIcon());
- assertEquals(null, si.getTitle());
- assertEquals(null, si.getText());
- assertEquals(null, si.getIntent());
- assertEquals(0, si.getWeight());
- assertEquals(null, si.getExtras());
-
- assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_KEY_FIELDS_ONLY, si.getFlags());
- assertEquals(null, si.getBitmapPath());
- assertEquals(0, si.getIconResourceId());
- }
-
-
- public void testCopyNonNullFieldsFrom() {
- PersistableBundle pb = new PersistableBundle();
- pb.putInt("k", 1);
- ShortcutInfo sorig = new ShortcutInfo.Builder(getContext())
- .setId("id")
- .setActivityComponent(new ComponentName("a", "b"))
- .setIcon(Icon.createWithContentUri("content://a.b.c/"))
- .setTitle("title")
- .setText("text")
- .setIntent(makeIntent("action", "key", "val"))
- .setWeight(123)
- .setExtras(pb)
- .build();
- sorig.addFlags(ShortcutInfo.FLAG_PINNED);
- sorig.setBitmapPath("abc");
- sorig.setIconResourceId(456);
-
- ShortcutInfo si;
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
- .setActivityComponent(new ComponentName("x", "y")).build());
- assertEquals(new ComponentName("x", "y"), si.getActivityComponent());
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
- .setIcon(Icon.createWithContentUri("content://x.y.z/")).build());
- assertEquals("content://x.y.z/", si.getIcon().getUriString());
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
- .setTitle("xyz").build());
- assertEquals("xyz", si.getTitle());
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
- .setText("xxx").build());
- assertEquals("xxx", si.getText());
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
- .setIntent(makeIntent("action2")).build());
- assertEquals("action2", si.getIntent().getAction());
- assertEquals(null, si.getIntent().getStringExtra("key"));
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
- .setIntent(makeIntent("action3", "key", "x")).build());
- assertEquals("action3", si.getIntent().getAction());
- assertEquals("x", si.getIntent().getStringExtra("key"));
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
- .setWeight(999).build());
- assertEquals(999, si.getWeight());
-
-
- PersistableBundle pb2 = new PersistableBundle();
- pb2.putInt("x", 99);
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
- .setExtras(pb2).build());
- assertEquals(99, si.getExtras().getInt("x"));
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
index 5d29242..0e2a80c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
@@ -57,7 +57,9 @@
import android.os.FileUtils;
import android.os.Handler;
import android.os.Looper;
+import android.os.Parcel;
import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
@@ -76,6 +78,7 @@
import com.android.server.pm.ShortcutService.ConfigConstants;
import com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
import com.android.server.pm.ShortcutUser.PackageWithUser;
+import com.android.server.testutis.TestUtils;
import libcore.io.IoUtils;
@@ -96,6 +99,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Consumer;
/**
* Tests for ShortcutService and ShortcutManager.
@@ -107,10 +111,8 @@
-w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
* TODO: Add checks with assertAllNotHaveIcon()
- *
- * TODO: separate, detailed tests for ShortcutInfo (CTS?) *
- *
- * TODO: Cross-user test (do in CTS?)
+ * TODO: Detailed test for hasShortcutPermissionInner().
+ * TODO: Add tests for the command line functions too.
*/
@SmallTest
public class ShortcutManagerTest extends InstrumentationTestCase {
@@ -122,6 +124,8 @@
*/
private static final boolean ENABLE_DUMP = false; // DO NOT SUBMIT WITH true
+ private static final boolean DUMP_ON_TEARDOWN = false; // DO NOT SUBMIT WITH true
+
// public for mockito
public class BaseContext extends MockContext {
@Override
@@ -261,7 +265,8 @@
@Override
boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
// Sort of hack; do a simpler check.
- return LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage);
+ return LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage)
+ || LAUNCHER_3.equals(callingPackage) || LAUNCHER_4.equals(callingPackage);
}
@Override
@@ -394,7 +399,7 @@
private Map<String, PackageInfo> mInjectedPackages;
- private ArrayList<PackageWithUser> mUninstalledPackages;
+ private Set<PackageWithUser> mUninstalledPackages;
private PackageManager mMockPackageManager;
private PackageManagerInternal mMockPackageManagerInternal;
@@ -409,12 +414,21 @@
private static final String CALLING_PACKAGE_3 = "com.android.test.3";
private static final int CALLING_UID_3 = 10003;
+ private static final String CALLING_PACKAGE_4 = "com.android.test.4";
+ private static final int CALLING_UID_4 = 10004;
+
private static final String LAUNCHER_1 = "com.android.launcher.1";
private static final int LAUNCHER_UID_1 = 10011;
private static final String LAUNCHER_2 = "com.android.launcher.2";
private static final int LAUNCHER_UID_2 = 10012;
+ private static final String LAUNCHER_3 = "com.android.launcher.3";
+ private static final int LAUNCHER_UID_3 = 10013;
+
+ private static final String LAUNCHER_4 = "com.android.launcher.4";
+ private static final int LAUNCHER_UID_4 = 10014;
+
private static final int USER_0 = UserHandle.USER_SYSTEM;
private static final int USER_10 = 10;
private static final int USER_11 = 11;
@@ -438,6 +452,13 @@
private static final int MAX_ICON_DIMENSION_LOWRAM = 32;
+ private static final ShortcutQuery QUERY_ALL = new ShortcutQuery();
+
+ static {
+ QUERY_ALL.setQueryFlags(
+ ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED);
+ }
+
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -457,10 +478,19 @@
addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 1);
addPackage(CALLING_PACKAGE_2, CALLING_UID_2, 2);
addPackage(CALLING_PACKAGE_3, CALLING_UID_3, 3);
+ addPackage(CALLING_PACKAGE_4, CALLING_UID_4, 10);
addPackage(LAUNCHER_1, LAUNCHER_UID_1, 4);
addPackage(LAUNCHER_2, LAUNCHER_UID_2, 5);
+ addPackage(LAUNCHER_3, LAUNCHER_UID_3, 6);
+ addPackage(LAUNCHER_4, LAUNCHER_UID_4, 10);
- mUninstalledPackages = new ArrayList<>();
+ // CALLING_PACKAGE_3 / LAUNCHER_3 are not backup target.
+ updatePackageInfo(CALLING_PACKAGE_3,
+ pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
+ updatePackageInfo(LAUNCHER_3,
+ pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
+
+ mUninstalledPackages = new HashSet<>();
mInjectedFilePathRoot = new File(getTestContext().getCacheDir(), "test-files");
@@ -475,6 +505,13 @@
setCaller(CALLING_PACKAGE_1);
}
+ @Override
+ protected void tearDown() throws Exception {
+ if (DUMP_ON_TEARDOWN) dumpsysOnLogcat("Teardown");
+
+ super.tearDown();
+ }
+
private Context getTestContext() {
return getInstrumentation().getContext();
}
@@ -504,6 +541,10 @@
return Arrays.asList(array);
}
+ private <T> Set<T> set(Set<T> in) {
+ return new ArraySet<T>(in);
+ }
+
private Signature[] genSignatures(String... signatures) {
final Signature[] sigs = new Signature[signatures.length];
for (int i = 0; i < signatures.length; i++){
@@ -529,10 +570,24 @@
mInjectedPackages.put(packageName, genPackage(packageName, uid, version, signatures));
}
+ private void updatePackageInfo(String packageName, Consumer<PackageInfo> c) {
+ c.accept(mInjectedPackages.get(packageName));
+ }
+
private void uninstallPackage(int userId, String packageName) {
+ if (ENABLE_DUMP) {
+ Log.i(TAG, "Unnstall package " + packageName + " / " + userId);
+ }
mUninstalledPackages.add(PackageWithUser.of(userId, packageName));
}
+ private void installPackage(int userId, String packageName) {
+ if (ENABLE_DUMP) {
+ Log.i(TAG, "Install package " + packageName + " / " + userId);
+ }
+ mUninstalledPackages.remove(PackageWithUser.of(userId, packageName));
+ }
+
PackageInfo getInjectedPackageInfo(String packageName, @UserIdInt int userId,
boolean getSignatures) {
final PackageInfo pi = mInjectedPackages.get(packageName);
@@ -591,14 +646,22 @@
/** For debugging */
private void dumpsysOnLogcat() {
- if (!ENABLE_DUMP) return;
+ dumpsysOnLogcat("");
+ }
+
+ private void dumpsysOnLogcat(String message) {
+ dumpsysOnLogcat(message, false);
+ }
+
+ private void dumpsysOnLogcat(String message, boolean force) {
+ if (force || !ENABLE_DUMP) return;
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final PrintWriter pw = new PrintWriter(out);
mService.dumpInner(pw);
pw.close();
- Log.e(TAG, "Dumping ShortcutService:");
+ Log.e(TAG, "Dumping ShortcutService: " + message);
for (String line : out.toString().split("\n")) {
Log.e(TAG, line);
}
@@ -608,9 +671,13 @@
* For debugging, dump arbitrary file on logcat.
*/
private void dumpFileOnLogcat(String path) {
+ dumpFileOnLogcat(path, "");
+ }
+
+ private void dumpFileOnLogcat(String path, String message) {
if (!ENABLE_DUMP) return;
- Log.i(TAG, "Dumping file: " + path);
+ Log.i(TAG, "Dumping file: " + path + " " + message);
final StringBuilder sb = new StringBuilder();
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
String line;
@@ -636,10 +703,14 @@
* For debugging, dump per-user state file on logcat.
*/
private void dumpUserFile(int userId) {
+ dumpUserFile(userId, "");
+ }
+
+ private void dumpUserFile(int userId, String message) {
mService.saveDirtyInfo();
dumpFileOnLogcat(mInjectedFilePathRoot.getAbsolutePath()
+ "/user-" + userId
- + "/" + ShortcutService.FILENAME_USER_PACKAGES);
+ + "/" + ShortcutService.FILENAME_USER_PACKAGES, message);
}
private void waitOnMainThread() throws Throwable {
@@ -1055,11 +1126,13 @@
return i;
}
- /**
- * Wrap a set in an ArraySet just to get a better toString.
- */
- private <T> Set<T> set(Set<T> in) {
- return new ArraySet<T>(in);
+ private ShortcutInfo parceled(ShortcutInfo si) {
+ Parcel p = Parcel.obtain();
+ p.writeParcelable(si, 0);
+ p.setDataPosition(0);
+ ShortcutInfo si2 = p.readParcelable(getClass().getClassLoader());
+ p.recycle();
+ return si2;
}
/**
@@ -1320,8 +1393,6 @@
// Still 2 calls left.
assertEquals(2, mManager.getRemainingCallCount());
-
- // TODO Make sure pinned shortcuts won't be deleted.
}
public void testDeleteAllDynamicShortcuts() {
@@ -1351,8 +1422,6 @@
// Still 1 call left
assertEquals(1, mManager.getRemainingCallCount());
-
- // TODO Make sure pinned shortcuts won't be deleted.
}
public void testThrottling() {
@@ -1877,9 +1946,6 @@
});
}
- // TODO: updateShortcuts()
- // TODO: getPinnedShortcuts()
-
// === Test for launcher side APIs ===
private static ShortcutQuery buildQuery(long changedSince,
@@ -1893,6 +1959,20 @@
return q;
}
+ private static ShortcutQuery buildAllQuery(String packageName) {
+ final ShortcutQuery q = new ShortcutQuery();
+ q.setPackage(packageName);
+ q.setQueryFlags(ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED);
+ return q;
+ }
+
+ private static ShortcutQuery buildPinnedQuery(String packageName) {
+ final ShortcutQuery q = new ShortcutQuery();
+ q.setPackage(packageName);
+ q.setQueryFlags(ShortcutQuery.FLAG_GET_PINNED);
+ return q;
+ }
+
public void testGetShortcuts() {
// Set up shortcuts.
@@ -3052,6 +3132,7 @@
// Remove CALLING_PACKAGE_2
reset(c0);
+ uninstallPackage(USER_0, CALLING_PACKAGE_2);
mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_0, USER_0);
// Should get a callback with an empty list.
@@ -3299,9 +3380,9 @@
// Check the registered packages.
dumpsysOnLogcat();
assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
- set(user0.getPackages().keySet()));
+ set(user0.getAllPackages().keySet()));
assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
- set(user10.getPackages().keySet()));
+ set(user10.getAllPackages().keySet()));
assertEquals(
makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
PackageWithUser.of(USER_0, LAUNCHER_2)),
@@ -3326,13 +3407,14 @@
mService.saveDirtyInfo();
// Nonexistent package.
+ uninstallPackage(USER_0, "abc");
mService.cleanUpPackageLocked("abc", USER_0, USER_0);
// No changes.
assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
- set(user0.getPackages().keySet()));
+ set(user0.getAllPackages().keySet()));
assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
- set(user10.getPackages().keySet()));
+ set(user10.getAllPackages().keySet()));
assertEquals(
makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
PackageWithUser.of(USER_0, LAUNCHER_2)),
@@ -3357,12 +3439,13 @@
mService.saveDirtyInfo();
// Remove a package.
+ uninstallPackage(USER_0, CALLING_PACKAGE_1);
mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0, USER_0);
assertEquals(makeSet(CALLING_PACKAGE_2),
- set(user0.getPackages().keySet()));
+ set(user0.getAllPackages().keySet()));
assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
- set(user10.getPackages().keySet()));
+ set(user10.getAllPackages().keySet()));
assertEquals(
makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
PackageWithUser.of(USER_0, LAUNCHER_2)),
@@ -3387,12 +3470,13 @@
mService.saveDirtyInfo();
// Remove a launcher.
+ uninstallPackage(USER_10, LAUNCHER_1);
mService.cleanUpPackageLocked(LAUNCHER_1, USER_10, USER_10);
assertEquals(makeSet(CALLING_PACKAGE_2),
- set(user0.getPackages().keySet()));
+ set(user0.getAllPackages().keySet()));
assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
- set(user10.getPackages().keySet()));
+ set(user10.getAllPackages().keySet()));
assertEquals(
makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
PackageWithUser.of(USER_0, LAUNCHER_2)),
@@ -3414,12 +3498,13 @@
mService.saveDirtyInfo();
// Remove a package.
+ uninstallPackage(USER_10, CALLING_PACKAGE_2);
mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_10, USER_10);
assertEquals(makeSet(CALLING_PACKAGE_2),
- set(user0.getPackages().keySet()));
+ set(user0.getAllPackages().keySet()));
assertEquals(makeSet(CALLING_PACKAGE_1),
- set(user10.getPackages().keySet()));
+ set(user10.getAllPackages().keySet()));
assertEquals(
makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
PackageWithUser.of(USER_0, LAUNCHER_2)),
@@ -3441,12 +3526,13 @@
mService.saveDirtyInfo();
// Remove the other launcher from user 10 too.
+ uninstallPackage(USER_10, LAUNCHER_2);
mService.cleanUpPackageLocked(LAUNCHER_2, USER_10, USER_10);
assertEquals(makeSet(CALLING_PACKAGE_2),
- set(user0.getPackages().keySet()));
+ set(user0.getAllPackages().keySet()));
assertEquals(makeSet(CALLING_PACKAGE_1),
- set(user10.getPackages().keySet()));
+ set(user10.getAllPackages().keySet()));
assertEquals(
makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
PackageWithUser.of(USER_0, LAUNCHER_2)),
@@ -3468,12 +3554,13 @@
mService.saveDirtyInfo();
// More remove.
+ uninstallPackage(USER_10, CALLING_PACKAGE_1);
mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_10, USER_10);
assertEquals(makeSet(CALLING_PACKAGE_2),
- set(user0.getPackages().keySet()));
+ set(user0.getAllPackages().keySet()));
assertEquals(makeSet(),
- set(user10.getPackages().keySet()));
+ set(user10.getAllPackages().keySet()));
assertEquals(
makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
PackageWithUser.of(USER_0, LAUNCHER_2)),
@@ -3494,97 +3581,6 @@
mService.saveDirtyInfo();
}
-
- public void testSaveAndLoadUser_forBackup() {
- // Create some shortcuts.
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
- runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
-
- // Pin some.
-
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s1"), HANDLE_USER_0);
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s2"), UserHandle.of(USER_P0));
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
- list("s3"), HANDLE_USER_0);
- });
-
- runWithCaller(LAUNCHER_1, USER_P0, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s2"), HANDLE_USER_0);
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s3"), UserHandle.of(USER_P0));
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
- list("s1"), HANDLE_USER_0);
- });
-
- runWithCaller(LAUNCHER_1, USER_10, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s3"), HANDLE_USER_10);
- });
-
- // Check the state.
-
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
-
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
-
- // Make sure all the information is persisted.
- mService.saveDirtyInfo();
- initService();
- mService.handleUnlockUser(USER_0);
- mService.handleUnlockUser(USER_P0);
- mService.handleUnlockUser(USER_10);
- }
-
public void testHandleGonePackage_crossProfile() {
// Create some shortcuts.
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -3841,13 +3837,9 @@
assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
}
- // TODO Detailed test for hasShortcutPermissionInner().
-
- // TODO Add tests for the command line functions too.
-
private void checkCanRestoreTo(boolean expected, ShortcutPackageInfo spi,
int version, String... signatures) {
- assertEquals(expected, spi.canRestoreTo(genPackage(
+ assertEquals(expected, spi.canRestoreTo(mService, genPackage(
"dummy", /* uid */ 0, version, signatures)));
}
@@ -3919,6 +3911,7 @@
assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
+ uninstallPackage(USER_0, CALLING_PACKAGE_1);
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageDeleteIntent(CALLING_PACKAGE_1, USER_0));
@@ -3929,6 +3922,7 @@
assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
+ uninstallPackage(USER_10, CALLING_PACKAGE_2);
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageDeleteIntent(CALLING_PACKAGE_2, USER_10));
@@ -3961,7 +3955,1163 @@
assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
}
- public void testHandlePackageUpdate() {
- // TODO: Make sure unshadow is called.
+ private void backupAndRestore() {
+ int prevUid = mInjectedCallingUid;
+
+ mInjectedCallingUid = Process.SYSTEM_UID; // Only system can call it.
+
+ dumpsysOnLogcat("Before backup");
+
+ final byte[] payload = mService.getBackupPayload(USER_0);
+ if (ENABLE_DUMP) {
+ final String xml = new String(payload);
+ Log.i(TAG, "Backup payload:");
+ for (String line : xml.split("\n")) {
+ Log.i(TAG, line);
+ }
+ }
+
+ // Before doing anything else, uninstall all packages.
+ for (int userId : list(USER_0, USER_P0)) {
+ for (String pkg : list(CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3,
+ LAUNCHER_1, LAUNCHER_2, LAUNCHER_3)) {
+ uninstallPackage(userId, pkg);
+ }
+ }
+
+ initService();
+ mService.applyRestore(payload, USER_0);
+
+ // handleUnlockUser will perform the gone package check, but it shouldn't remove
+ // shadow information.
+ mService.handleUnlockUser(USER_0);
+
+ dumpsysOnLogcat("After restore");
+
+ mInjectedCallingUid = prevUid;
+ }
+
+ private void prepareCrossProfileDataSet() {
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+ makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+ });
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+ makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+ });
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+ makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+ });
+ runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list()));
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+ makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("x1"), makeShortcut("x2"), makeShortcut("x3"),
+ makeShortcut("x4"), makeShortcut("x5"), makeShortcut("x6"))));
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s1", "s2"), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s1", "s2", "s3"), HANDLE_USER_0);
+
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1", "s4"), HANDLE_USER_P0);
+ });
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s2", "s3"), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s2", "s3", "s4"), HANDLE_USER_0);
+
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2", "s5"), HANDLE_USER_P0);
+ });
+ runWithCaller(LAUNCHER_3, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s3", "s4"), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s3", "s4", "s5"), HANDLE_USER_0);
+
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3", "s6"), HANDLE_USER_P0);
+ });
+ runWithCaller(LAUNCHER_4, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list(), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list(), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list(), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_4, list(), HANDLE_USER_0);
+ });
+
+ // Launcher on a managed profile is referring ot user 0!
+ runWithCaller(LAUNCHER_1, USER_P0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3", "s4"), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s3", "s4", "s5"), HANDLE_USER_0);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s3", "s4", "s5", "s6"),
+ HANDLE_USER_0);
+
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s4", "s1"), HANDLE_USER_P0);
+ });
+ runWithCaller(LAUNCHER_1, USER_10, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("x4", "x5"), HANDLE_USER_10);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("x4", "x5", "x6"), HANDLE_USER_10);
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("x4", "x5", "x6", "x1"),
+ HANDLE_USER_10);
+ });
+ }
+
+ private void prepareForBackupTest() {
+
+ prepareCrossProfileDataSet();
+
+ backupAndRestore();
+ }
+
+ private void assertExistsAndShadow(ShortcutPackageItem spi) {
+ assertNotNull(spi);
+ assertTrue(spi.getPackageInfo().isShadow());
+ }
+
+ /**
+ * Make sure the backup data doesn't have the following information:
+ * - Launchers on other users.
+ * - Non-backup app information.
+ *
+ * But restores all other infomation.
+ *
+ * It also omits the following pieces of information, but that's tested in
+ * {@link #testShortcutInfoSaveAndLoad_forBackup}.
+ * - Unpinned dynamic shortcuts
+ * - Bitmaps
+ */
+ public void testBackupAndRestore() {
+ prepareForBackupTest();
+
+ checkBackupAndRestore_success();
+ }
+
+ public void testBackupAndRestore_backupRestoreTwice() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ dumpsysOnLogcat("Before second backup");
+
+ backupAndRestore();
+
+ dumpsysOnLogcat("After second backup");
+
+ checkBackupAndRestore_success();
+ }
+
+ public void testBackupAndRestore_backupRestoreMultiple() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ backupAndRestore();
+
+ // This also shouldn't affect the result.
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+ makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+ });
+
+ backupAndRestore();
+
+ checkBackupAndRestore_success();
+ }
+
+ public void testBackupAndRestore_restoreToNewVersion() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ backupAndRestore();
+
+ addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 2);
+ addPackage(LAUNCHER_1, LAUNCHER_UID_1, 5);
+
+ checkBackupAndRestore_success();
+ }
+
+ public void testBackupAndRestore_restoreToSuperSetSignatures() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ backupAndRestore();
+
+ addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 1, "sigx", CALLING_PACKAGE_1);
+ addPackage(LAUNCHER_1, LAUNCHER_UID_1, 4, LAUNCHER_1, "sigy");
+
+ checkBackupAndRestore_success();
+ }
+
+ private void checkBackupAndRestore_success() {
+ // Make sure non-system user is not restored.
+ final ShortcutUser userP0 = mService.getUserShortcutsLocked(USER_P0);
+ assertEquals(0, userP0.getAllPackages().size());
+ assertEquals(0, userP0.getAllLaunchers().size());
+
+ // Make sure only "allowBackup" apps are restored, and are shadow.
+ final ShortcutUser user0 = mService.getUserShortcutsLocked(USER_0);
+ assertExistsAndShadow(user0.getAllPackages().get(CALLING_PACKAGE_1));
+ assertExistsAndShadow(user0.getAllPackages().get(CALLING_PACKAGE_2));
+ assertExistsAndShadow(user0.getAllLaunchers().get(PackageWithUser.of(USER_0, LAUNCHER_1)));
+ assertExistsAndShadow(user0.getAllLaunchers().get(PackageWithUser.of(USER_0, LAUNCHER_2)));
+
+ assertNull(user0.getAllPackages().get(CALLING_PACKAGE_3));
+ assertNull(user0.getAllLaunchers().get(PackageWithUser.of(USER_0, LAUNCHER_3)));
+ assertNull(user0.getAllLaunchers().get(PackageWithUser.of(USER_P0, LAUNCHER_1)));
+
+ installPackage(USER_0, CALLING_PACKAGE_1);
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertShortcutIds(assertAllPinned(
+ mManager.getPinnedShortcuts()),
+ "s1", "s2");
+ });
+
+ installPackage(USER_0, LAUNCHER_1);
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+ "s1");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+ /* empty, not restored */ );
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty, not restored */ );
+
+ assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size());
+ });
+
+ installPackage(USER_0, CALLING_PACKAGE_2);
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertShortcutIds(assertAllPinned(
+ mManager.getPinnedShortcuts()),
+ "s1", "s2", "s3");
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+ "s1");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s1", "s2");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty, not restored */ );
+
+ assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size());
+ });
+
+ // 3 shouldn't be backed up, so no pinned shortcuts.
+ installPackage(USER_0, CALLING_PACKAGE_3);
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertEquals(0, mManager.getPinnedShortcuts().size());
+ });
+
+ // Launcher on a different profile shouldn't be restored.
+ runWithCaller(LAUNCHER_1, USER_P0, () -> {
+ assertEquals(0,
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)
+ .size());
+ assertEquals(0,
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)
+ .size());
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* wasn't restored, so still empty */ );
+ });
+
+ // Package on a different profile, no restore.
+ installPackage(USER_P0, CALLING_PACKAGE_1);
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertEquals(0, mManager.getPinnedShortcuts().size());
+ });
+
+ // Restore launcher 2 on user 0.
+ installPackage(USER_0, LAUNCHER_2);
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+ "s2");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s2", "s3");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* wasn't restored, so still empty */ );
+
+ assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size());
+ });
+
+
+ // Restoration of launcher2 shouldn't affect other packages; so do the same checks and
+ // make sure they still have the same result.
+ installPackage(USER_0, CALLING_PACKAGE_1);
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertShortcutIds(assertAllPinned(
+ mManager.getPinnedShortcuts()),
+ "s1", "s2");
+ });
+
+ installPackage(USER_0, LAUNCHER_1);
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+ "s1");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s1", "s2");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* wasn't restored, so still empty */ );
+
+ assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size());
+ });
+
+ installPackage(USER_0, CALLING_PACKAGE_2);
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertShortcutIds(assertAllPinned(
+ mManager.getPinnedShortcuts()),
+ "s1", "s2", "s3");
+ });
+ }
+
+ public void testBackupAndRestore_publisherLowerVersion() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ backupAndRestore();
+
+ addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 0); // Lower version
+
+ checkBackupAndRestore_publisherNotRestored();
+ }
+
+ public void testBackupAndRestore_publisherWrongSignature() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ backupAndRestore();
+
+ addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sigx"); // different signature
+
+ checkBackupAndRestore_publisherNotRestored();
+ }
+
+ public void testBackupAndRestore_publisherNoLongerBackupTarget() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ backupAndRestore();
+
+ updatePackageInfo(CALLING_PACKAGE_1,
+ pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
+
+ checkBackupAndRestore_publisherNotRestored();
+ }
+
+ private void checkBackupAndRestore_publisherNotRestored() {
+ installPackage(USER_0, CALLING_PACKAGE_1);
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertEquals(0, mManager.getPinnedShortcuts().size());
+ });
+
+ installPackage(USER_0, CALLING_PACKAGE_2);
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertShortcutIds(assertAllPinned(
+ mManager.getPinnedShortcuts()),
+ "s1", "s2", "s3");
+ });
+
+ installPackage(USER_0, LAUNCHER_1);
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s1", "s2");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+ installPackage(USER_0, LAUNCHER_2);
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s2", "s3");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+
+ installPackage(USER_0, CALLING_PACKAGE_3);
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertEquals(0, mManager.getPinnedShortcuts().size());
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s1", "s2");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s2", "s3");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+ }
+
+ public void testBackupAndRestore_launcherLowerVersion() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ backupAndRestore();
+
+ addPackage(LAUNCHER_1, LAUNCHER_UID_1, 0); // Lower version
+
+ checkBackupAndRestore_launcherNotRestored();
+ }
+
+ public void testBackupAndRestore_launcherWrongSignature() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ backupAndRestore();
+
+ addPackage(LAUNCHER_1, LAUNCHER_UID_1, 10, "sigx"); // different signature
+
+ checkBackupAndRestore_launcherNotRestored();
+ }
+
+ public void testBackupAndRestore_launcherNoLongerBackupTarget() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ backupAndRestore();
+
+ updatePackageInfo(LAUNCHER_1,
+ pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
+
+ checkBackupAndRestore_launcherNotRestored();
+ }
+
+ private void checkBackupAndRestore_launcherNotRestored() {
+ installPackage(USER_0, CALLING_PACKAGE_1);
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+
+ // s1 was pinned by launcher 1, which is not restored, yet, so we still see "s1" here.
+ assertShortcutIds(assertAllPinned(
+ mManager.getPinnedShortcuts()),
+ "s1", "s2");
+ });
+
+ installPackage(USER_0, CALLING_PACKAGE_2);
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertShortcutIds(assertAllPinned(
+ mManager.getPinnedShortcuts()),
+ "s1", "s2", "s3");
+ });
+
+ // Now we try to restore launcher 1. Then we realize it's not restorable, so L1 has no pinned
+ // shortcuts.
+ installPackage(USER_0, LAUNCHER_1);
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+
+ // Now CALLING_PACKAGE_1 realizes "s1" is no longer pinned.
+ assertShortcutIds(assertAllPinned(
+ mManager.getPinnedShortcuts()),
+ "s2");
+ });
+
+ installPackage(USER_0, LAUNCHER_2);
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+ "s2");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s2", "s3");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+
+ installPackage(USER_0, CALLING_PACKAGE_3);
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertEquals(0, mManager.getPinnedShortcuts().size());
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
+ "s2");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s2", "s3");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+ }
+
+ public void testBackupAndRestore_launcherAndPackageNoLongerBackupTarget() {
+ prepareForBackupTest();
+
+ // Note doing a backup & restore again here shouldn't affect the result.
+ backupAndRestore();
+
+ updatePackageInfo(CALLING_PACKAGE_1,
+ pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
+
+ updatePackageInfo(LAUNCHER_1,
+ pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
+
+ checkBackupAndRestore_publisherAndLauncherNotRestored();
+ }
+
+ private void checkBackupAndRestore_publisherAndLauncherNotRestored() {
+ installPackage(USER_0, CALLING_PACKAGE_1);
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertEquals(0, mManager.getPinnedShortcuts().size());
+ });
+
+ installPackage(USER_0, CALLING_PACKAGE_2);
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertShortcutIds(assertAllPinned(
+ mManager.getPinnedShortcuts()),
+ "s1", "s2", "s3");
+ });
+
+ installPackage(USER_0, LAUNCHER_1);
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+ installPackage(USER_0, LAUNCHER_2);
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s2", "s3");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+
+ // Because launcher 1 wasn't restored, "s1" is no longer pinned.
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertShortcutIds(assertAllPinned(
+ mManager.getPinnedShortcuts()),
+ "s2", "s3");
+ });
+
+ installPackage(USER_0, CALLING_PACKAGE_3);
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertEquals(0, mManager.getDynamicShortcuts().size());
+ assertEquals(0, mManager.getPinnedShortcuts().size());
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+ /* empty */);
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
+ "s2", "s3");
+ assertShortcutIds(assertAllPinned(
+ mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+ /* empty */);
+ });
+ }
+
+ public void testSaveAndLoad_crossProfile() {
+ prepareCrossProfileDataSet();
+
+ dumpsysOnLogcat("Before save & load");
+
+ mService.saveDirtyInfo();
+ initService();
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
+ "s1", "s2", "s3", "s4", "s5", "s6");
+ assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
+ "s1", "s2", "s3", "s4");
+ });
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
+ "s1", "s2", "s3", "s4", "s5", "s6");
+ assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
+ "s1", "s2", "s3", "s4", "s5");
+ });
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
+ "s1", "s2", "s3", "s4", "s5", "s6");
+ assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
+ "s1", "s2", "s3", "s4", "s5", "s6");
+ });
+ runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
+ assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts())
+ /* empty */);
+ assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts())
+ /* empty */);
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
+ "s1", "s2", "s3", "s4", "s5", "s6");
+ assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
+ "s1", "s2", "s3", "s4", "s5", "s6");
+ });
+ runWithCaller(CALLING_PACKAGE_2, USER_P0, () -> {
+ assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts())
+ /* empty */);
+ assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts())
+ /* empty */);
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
+ "x1", "x2", "x3", "x4", "x5", "x6");
+ assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
+ "x4", "x5");
+ });
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0),
+ "s1");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0),
+ "s1", "s2");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0),
+ "s1", "s2", "s3");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0)
+ /* empty */);
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0),
+ "s1", "s4");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0)
+ /* empty */);
+ TestUtils.assertExpectException(
+ SecurityException.class, "", () -> {
+ mLauncherApps.getShortcuts(
+ buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_10);
+ });
+ });
+ runWithCaller(LAUNCHER_2, USER_0, () -> {
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0),
+ "s2");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0),
+ "s2", "s3");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0),
+ "s2", "s3", "s4");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0)
+ /* empty */);
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0),
+ "s2", "s5");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0)
+ /* empty */);
+ });
+ runWithCaller(LAUNCHER_3, USER_0, () -> {
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0),
+ "s3");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0),
+ "s3", "s4");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0),
+ "s3", "s4", "s5");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0)
+ /* empty */);
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0),
+ "s3", "s6");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0)
+ /* empty */);
+ });
+ runWithCaller(LAUNCHER_4, USER_0, () -> {
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0)
+ /* empty */);
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0)
+ /* empty */);
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0)
+ /* empty */);
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0)
+ /* empty */);
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0)
+ /* empty */);
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0)
+ /* empty */);
+ });
+ runWithCaller(LAUNCHER_1, USER_P0, () -> {
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0),
+ "s3", "s4");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0),
+ "s3", "s4", "s5");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0),
+ "s3", "s4", "s5", "s6");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0),
+ "s1", "s4");
+ TestUtils.assertExpectException(
+ SecurityException.class, "you need to be SYSTEM", () -> {
+ mLauncherApps.getShortcuts(
+ buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_10);
+ });
+ });
+ runWithCaller(LAUNCHER_1, USER_10, () -> {
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_10),
+ "x4", "x5");
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_10)
+ /* empty */);
+ assertShortcutIds(
+ mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_10)
+ /* empty */);
+ TestUtils.assertExpectException(
+ SecurityException.class, "you need to be SYSTEM", () -> {
+ mLauncherApps.getShortcuts(
+ buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0);
+ });
+ TestUtils.assertExpectException(
+ SecurityException.class, "you need to be SYSTEM", () -> {
+ mLauncherApps.getShortcuts(
+ buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_P0);
+ });
+ });
+ }
+
+ // ShortcutInfo tests
+
+ public void testShortcutInfoMissingMandatoryFields() {
+ TestUtils.assertExpectException(
+ IllegalArgumentException.class,
+ "ID must be provided",
+ () -> new ShortcutInfo.Builder(getTestContext()).build());
+ TestUtils.assertExpectException(
+ IllegalArgumentException.class,
+ "title must be provided",
+ () -> new ShortcutInfo.Builder(getTestContext()).setId("id").build()
+ .enforceMandatoryFields());
+ TestUtils.assertExpectException(
+ NullPointerException.class,
+ "Intent must be provided",
+ () -> new ShortcutInfo.Builder(getTestContext()).setId("id").setTitle("x").build()
+ .enforceMandatoryFields());
+ }
+
+ public void testShortcutInfoParcel() {
+ ShortcutInfo si = parceled(new ShortcutInfo.Builder(getTestContext())
+ .setId("id")
+ .setTitle("title")
+ .setIntent(makeIntent("action", ShortcutActivity.class))
+ .build());
+ assertEquals(getTestContext().getPackageName(), si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals("title", si.getTitle());
+ assertEquals("action", si.getIntent().getAction());
+
+ PersistableBundle pb = new PersistableBundle();
+ pb.putInt("k", 1);
+
+ si = new ShortcutInfo.Builder(getTestContext())
+ .setId("id")
+ .setActivityComponent(new ComponentName("a", "b"))
+ .setIcon(Icon.createWithContentUri("content://a.b.c/"))
+ .setTitle("title")
+ .setText("text")
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setWeight(123)
+ .setExtras(pb)
+ .build();
+ si.addFlags(ShortcutInfo.FLAG_PINNED);
+ si.setBitmapPath("abc");
+ si.setIconResourceId(456);
+
+ si = parceled(si);
+
+ assertEquals(getTestContext().getPackageName(), si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+ assertEquals("content://a.b.c/", si.getIcon().getUriString());
+ assertEquals("title", si.getTitle());
+ assertEquals("text", si.getText());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getWeight());
+ assertEquals(1, si.getExtras().getInt("k"));
+
+ assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+ assertEquals("abc", si.getBitmapPath());
+ assertEquals(456, si.getIconResourceId());
+ }
+
+ public void testShortcutInfoClone() {
+ PersistableBundle pb = new PersistableBundle();
+ pb.putInt("k", 1);
+ ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext())
+ .setId("id")
+ .setActivityComponent(new ComponentName("a", "b"))
+ .setIcon(Icon.createWithContentUri("content://a.b.c/"))
+ .setTitle("title")
+ .setText("text")
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setWeight(123)
+ .setExtras(pb)
+ .build();
+ sorig.addFlags(ShortcutInfo.FLAG_PINNED);
+ sorig.setBitmapPath("abc");
+ sorig.setIconResourceId(456);
+
+ ShortcutInfo si = sorig.clone(/* clone flags*/ 0);
+
+ assertEquals(getTestContext().getPackageName(), si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+ assertEquals("content://a.b.c/", si.getIcon().getUriString());
+ assertEquals("title", si.getTitle());
+ assertEquals("text", si.getText());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getWeight());
+ assertEquals(1, si.getExtras().getInt("k"));
+
+ assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+ assertEquals("abc", si.getBitmapPath());
+ assertEquals(456, si.getIconResourceId());
+
+ si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
+
+ assertEquals(getTestContext().getPackageName(), si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+ assertEquals(null, si.getIcon());
+ assertEquals("title", si.getTitle());
+ assertEquals("text", si.getText());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getWeight());
+ assertEquals(1, si.getExtras().getInt("k"));
+
+ assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+ assertEquals(null, si.getBitmapPath());
+ assertEquals(0, si.getIconResourceId());
+
+ si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
+
+ assertEquals(getTestContext().getPackageName(), si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+ assertEquals(null, si.getIcon());
+ assertEquals("title", si.getTitle());
+ assertEquals("text", si.getText());
+ assertEquals(null, si.getIntent());
+ assertEquals(123, si.getWeight());
+ assertEquals(1, si.getExtras().getInt("k"));
+
+ assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+ assertEquals(null, si.getBitmapPath());
+ assertEquals(0, si.getIconResourceId());
+
+ si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
+
+ assertEquals(getTestContext().getPackageName(), si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals(null, si.getActivityComponent());
+ assertEquals(null, si.getIcon());
+ assertEquals(null, si.getTitle());
+ assertEquals(null, si.getText());
+ assertEquals(null, si.getIntent());
+ assertEquals(0, si.getWeight());
+ assertEquals(null, si.getExtras());
+
+ assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_KEY_FIELDS_ONLY, si.getFlags());
+ assertEquals(null, si.getBitmapPath());
+ assertEquals(0, si.getIconResourceId());
+ }
+
+ public void testShortcutInfoCopyNonNullFieldsFrom() throws InterruptedException {
+ PersistableBundle pb = new PersistableBundle();
+ pb.putInt("k", 1);
+ ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext())
+ .setId("id")
+ .setActivityComponent(new ComponentName("a", "b"))
+ .setIcon(Icon.createWithContentUri("content://a.b.c/"))
+ .setTitle("title")
+ .setText("text")
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setWeight(123)
+ .setExtras(pb)
+ .build();
+ sorig.addFlags(ShortcutInfo.FLAG_PINNED);
+ sorig.setBitmapPath("abc");
+ sorig.setIconResourceId(456);
+
+ ShortcutInfo si;
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setActivityComponent(new ComponentName("x", "y")).build());
+ assertEquals(new ComponentName("x", "y"), si.getActivityComponent());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setIcon(Icon.createWithContentUri("content://x.y.z/")).build());
+ assertEquals("content://x.y.z/", si.getIcon().getUriString());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setTitle("xyz").build());
+ assertEquals("xyz", si.getTitle());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setText("xxx").build());
+ assertEquals("xxx", si.getText());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setIntent(makeIntent("action2", ShortcutActivity.class)).build());
+ assertEquals("action2", si.getIntent().getAction());
+ assertEquals(null, si.getIntent().getStringExtra("key"));
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setIntent(makeIntent("action3", ShortcutActivity.class, "key", "x")).build());
+ assertEquals("action3", si.getIntent().getAction());
+ assertEquals("x", si.getIntent().getStringExtra("key"));
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setWeight(999).build());
+ assertEquals(999, si.getWeight());
+
+
+ PersistableBundle pb2 = new PersistableBundle();
+ pb2.putInt("x", 99);
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setExtras(pb2).build());
+ assertEquals(99, si.getExtras().getInt("x"));
+
+ final long timestamp = si.getLastChangedTimestamp();
+ Thread.sleep(2);
+
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setTitle("xyz").build());
+
+ assertTrue(si.getLastChangedTimestamp() > timestamp);
+ }
+
+ public void testShortcutInfoSaveAndLoad() throws InterruptedException {
+ setCaller(CALLING_PACKAGE_1, USER_0);
+
+ final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getTestContext().getResources(), R.drawable.black_32x32));
+
+ PersistableBundle pb = new PersistableBundle();
+ pb.putInt("k", 1);
+ ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
+ .setId("id")
+ .setActivityComponent(new ComponentName(mClientContext, ShortcutActivity2.class))
+ .setIcon(bmp32x32)
+ .setTitle("title")
+ .setText("text")
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setWeight(123)
+ .setExtras(pb)
+ .build();
+
+ mManager.addDynamicShortcut(sorig);
+
+ Thread.sleep(2);
+ final long now = System.currentTimeMillis();
+
+ // Save and load.
+ mService.saveDirtyInfo();
+ initService();
+ mService.handleUnlockUser(USER_0);
+
+ ShortcutInfo si;
+ si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_0);
+
+ assertEquals(CALLING_PACKAGE_1, si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals(ShortcutActivity2.class.getName(), si.getActivityComponent().getClassName());
+ assertEquals(null, si.getIcon());
+ assertEquals("title", si.getTitle());
+ assertEquals("text", si.getText());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getWeight());
+ 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());
+ assertTrue(si.getLastChangedTimestamp() < now);
+ }
+
+ public void testShortcutInfoSaveAndLoad_forBackup() {
+ setCaller(CALLING_PACKAGE_1, USER_0);
+
+ final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getTestContext().getResources(), R.drawable.black_32x32));
+
+ PersistableBundle pb = new PersistableBundle();
+ pb.putInt("k", 1);
+ ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
+ .setId("id")
+ .setActivityComponent(new ComponentName(mClientContext, ShortcutActivity2.class))
+ .setIcon(bmp32x32)
+ .setTitle("title")
+ .setText("text")
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setWeight(123)
+ .setExtras(pb)
+ .build();
+
+ mManager.addDynamicShortcut(sorig);
+
+ // 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"), HANDLE_USER_0);
+
+ // Do backup & restore.
+ backupAndRestore();
+
+ ShortcutInfo si;
+ si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_0);
+
+ assertEquals(CALLING_PACKAGE_1, si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals(ShortcutActivity2.class.getName(), si.getActivityComponent().getClassName());
+ assertEquals(null, si.getIcon());
+ assertEquals("title", si.getTitle());
+ assertEquals("text", si.getText());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getWeight());
+ assertEquals(1, si.getExtras().getInt("k"));
+
+ assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+ assertNull(si.getBitmapPath()); // No icon.
+ assertEquals(0, si.getIconResourceId());
+ }
+
+ public void testDumpsys_crossProfile() {
+ prepareCrossProfileDataSet();
+ dumpsysOnLogcat("test1", /* force= */ true);
+ }
+
+ public void testDumpsys_withIcons() {
+ testIcons();
+ // Dump after having some icons.
+ dumpsysOnLogcat("test1", /* force= */ true);
}
}