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 0000000000000..0b3f2aeac35a0 --- /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 181ed51443100..2d12fcda87c14 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 @@ public class SystemBackupAgent extends BackupAgentHelper { 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 @@ public class SystemBackupAgent extends BackupAgentHelper { 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 @@ public class SystemBackupAgent extends BackupAgentHelper { 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 a0fbc37a4acee..0000000000000 --- 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 7699f305fb17d..c6d66fe7fee58 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.annotation.UserIdInt; 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 org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.List; /** @@ -43,15 +48,16 @@ class ShortcutLauncher extends ShortcutPackageItem { 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> mPinnedShortcuts = new ArrayMap<>(); + final private ArrayMap> 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 @@ class ShortcutLauncher extends ShortcutPackageItem { 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 @@ class ShortcutLauncher extends ShortcutPackageItem { 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 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 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 prevSet = mPinnedShortcuts.get(packageName); + final ArraySet 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 @@ class ShortcutLauncher extends ShortcutPackageItem { newSet.add(id); } } - mPinnedShortcuts.put(packageName, newSet); + mPinnedShortcuts.put(pu, newSet); } packageShortcuts.refreshPinnedFlags(s); } @@ -101,12 +129,13 @@ class ShortcutLauncher extends ShortcutPackageItem { /** * Return the pinned shortcut IDs for the publisher package. */ - public ArraySet getPinnedShortcutIds(@NonNull String packageName) { - return mPinnedShortcuts.get(packageName); + public ArraySet 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 @@ class ShortcutLauncher extends ShortcutPackageItem { 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 ids = mPinnedShortcuts.valueAt(i); final int idSize = ids.size(); @@ -157,8 +192,6 @@ class ShortcutLauncher extends ShortcutPackageItem { final ShortcutLauncher ret = new ShortcutLauncher(launcherUserId, launcherPackageName, launcherUserId); - ShortcutPackageInfo spi = null; - ArraySet ids = null; final int outerDepth = parser.getDepth(); int type; @@ -172,13 +205,17 @@ class ShortcutLauncher extends ShortcutPackageItem { 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 @@ class ShortcutLauncher extends ShortcutPackageItem { 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 @@ class ShortcutLauncher extends ShortcutPackageItem { 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 @@ class ShortcutLauncher extends ShortcutPackageItem { 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 ids = mPinnedShortcuts.valueAt(i); final int idSize = ids.size(); @@ -233,4 +276,9 @@ class ShortcutLauncher extends ShortcutPackageItem { } } } + + @VisibleForTesting + ArraySet 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 5916202a0bdbb..1076a7a0ae883 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.ArrayMap; 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.File; 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 @@ class ShortcutPackage extends ShortcutPackageItem { */ 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 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 @@ class ShortcutPackage extends ShortcutPackageItem { public void findAll(@NonNull ShortcutService s, @NonNull List result, @Nullable Predicate 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 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 @@ class ShortcutPackage extends ShortcutPackageItem { || ((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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { } ShortcutService.warnForInvalidTag(depth, tag); } - if (spi != null) { - ret.replacePackageInfo(spi); - } return ret; } @@ -567,4 +590,9 @@ class ShortcutPackage extends ShortcutPackageItem { intentPersistableExtras, weight, extras, lastChangedTimestamp, flags, iconRes, bitmapPath); } + + @VisibleForTesting + List 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 5f706b83271e1..2c45890a9c408 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 @@ class ShortcutPackageInfo { return mIsShadow; } - public boolean isInstalled() { - return !mIsShadow; - } - public void setShadow(boolean shadow) { mIsShadow = shadow; } @@ -79,14 +75,24 @@ class ShortcutPackageInfo { 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 @@ class ShortcutPackageInfo { } 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 @@ class ShortcutPackageInfo { 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 hashes = new ArrayList<>(); - final int outerDepth = parser.getDepth(); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT @@ -163,7 +176,11 @@ class ShortcutPackageInfo { } 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 de2709d3d7acd..f31dd17d6288a 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 org.xmlpull.v1.XmlSerializer; 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 @@ abstract class ShortcutPackageItem { 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 76a2dfadc2645..7aefcb82a1582 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.graphics.drawable.Icon; 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.net.URISyntaxException; 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 @@ import java.util.function.Predicate; * * - 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 @@ public class ShortcutService extends IShortcutService.Stub { | 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { @GuardedBy("mLock") @NonNull - boolean isUserLoadedLocked(@UserIdInt int userId) { + private boolean isUserLoadedLocked(@UserIdInt int userId) { return mUsers.get(userId) != null; } @@ -841,19 +869,27 @@ public class ShortcutService extends IShortcutService.Stub { return userPackages; } + void forEachLoadedUserLocked(@NonNull Consumer 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 @@ public class ShortcutService extends IShortcutService.Stub { synchronized (mLock) { final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); - ps.ensureNotShadowAndSave(this); - // Throttling. if (!ps.tryApiCall(this)) { return false; @@ -1249,8 +1283,6 @@ public class ShortcutService extends IShortcutService.Stub { synchronized (mLock) { final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); - ps.ensureNotShadowAndSave(this); - // Throttling. if (!ps.tryApiCall(this)) { return false; @@ -1288,8 +1320,6 @@ public class ShortcutService extends IShortcutService.Stub { synchronized (mLock) { final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); - ps.ensureNotShadowAndSave(this); - // Throttling. if (!ps.tryApiCall(this)) { return false; @@ -1422,15 +1452,17 @@ public class ShortcutService extends IShortcutService.Stub { @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 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { // === 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 @@ public class ShortcutService extends IShortcutService.Stub { // 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 @@ public class ShortcutService extends IShortcutService.Stub { // Then remove pinned shortcuts from all launchers. final ArrayMap 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 @@ public class ShortcutService extends IShortcutService.Stub { * Entry point from {@link LauncherApps}. */ private class LocalService extends ShortcutServiceInternal { + @Override public List getShortcuts(int launcherUserId, @NonNull String callingPackage, long changedSince, @@ -1551,13 +1589,16 @@ public class ShortcutService extends IShortcutService.Stub { : 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 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 @@ public class ShortcutService extends IShortcutService.Stub { final ArrayList ret = new ArrayList<>(ids.size()); final ArraySet 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { * 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 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { @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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { return null; } finally { injectRestoreCallingIdentity(token); + + logDurationStat(Stats.GET_APPLICATION_INFO, start); } } @@ -1869,7 +1935,7 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { } @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 @@ public class ShortcutService extends IShortcutService.Stub { } 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { } 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 487558f81e451..593f607f62688 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 @@ class ShortcutUser { 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 @@ class ShortcutUser { @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 mPackages = new ArrayMap<>(); @@ -95,10 +95,18 @@ class ShortcutUser { mUserId = userId; } - public ArrayMap getPackages() { + public int getUserId() { + return mUserId; + } + + public ArrayMap getAllPackages() { return mPackages; } + public ShortcutPackage removePackage(@NonNull String packageName) { + return mPackages.remove(packageName); + } + public ArrayMap getAllLaunchers() { return mLaunchers; } @@ -113,22 +121,26 @@ class ShortcutUser { 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 @@ class ShortcutUser { } } - 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 callback) { forAllPackageItems(spi -> { @@ -166,6 +170,13 @@ class ShortcutUser { }); } + 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 @@ class ShortcutUser { 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 c44ffa481f8bb..0000000000000 --- 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 5d2924283ef73..0e2a80c8b3028 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.Bundle; 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.LauncherAppsService.LauncherAppsImpl; 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.HashSet; 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 @@ import java.util.Set; -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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { */ 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { @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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { private Map mInjectedPackages; - private ArrayList mUninstalledPackages; + private Set mUninstalledPackages; private PackageManager mMockPackageManager; private PackageManagerInternal mMockPackageManagerInternal; @@ -409,12 +414,21 @@ public class ShortcutManagerTest extends InstrumentationTestCase { 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { return Arrays.asList(array); } + private Set set(Set in) { + return new ArraySet(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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mInjectedPackages.put(packageName, genPackage(packageName, uid, version, signatures)); } + private void updatePackageInfo(String packageName, Consumer 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { /** 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { * 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { * 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { return i; } - /** - * Wrap a set in an ArraySet just to get a better toString. - */ - private Set set(Set in) { - return new ArraySet(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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // Still 2 calls left. assertEquals(2, mManager.getRemainingCallCount()); - - // TODO Make sure pinned shortcuts won't be deleted. } public void testDeleteAllDynamicShortcuts() { @@ -1351,8 +1422,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // Still 1 call left assertEquals(1, mManager.getRemainingCallCount()); - - // TODO Make sure pinned shortcuts won't be deleted. } public void testThrottling() { @@ -1877,9 +1946,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { }); } - // TODO: updateShortcuts() - // TODO: getPinnedShortcuts() - // === Test for launcher side APIs === private static ShortcutQuery buildQuery(long changedSince, @@ -1893,6 +1959,20 @@ public class ShortcutManagerTest extends InstrumentationTestCase { 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { 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 @@ public class ShortcutManagerTest extends InstrumentationTestCase { 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); } }