diff --git a/services/core/java/com/android/server/pm/ShortcutBackupAgent.java b/services/core/java/com/android/server/pm/ShortcutBackupAgent.java new file mode 100644 index 0000000000000..a0fbc37a4acee --- /dev/null +++ b/services/core/java/com/android/server/pm/ShortcutBackupAgent.java @@ -0,0 +1,37 @@ +/* + * 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 f1920c7e8e227..740a8f7681398 100644 --- a/services/core/java/com/android/server/pm/ShortcutLauncher.java +++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java @@ -32,7 +32,7 @@ import java.util.List; /** * Launcher information used by {@link ShortcutService}. */ -class ShortcutLauncher { +class ShortcutLauncher implements ShortcutPackageItem { private static final String TAG = ShortcutService.TAG; static final String TAG_ROOT = "launcher-pins"; @@ -44,10 +44,10 @@ class ShortcutLauncher { private static final String ATTR_PACKAGE_NAME = "package-name"; @UserIdInt - final int mUserId; + private final int mUserId; @NonNull - final String mPackageName; + private final String mPackageName; /** * Package name -> IDs. @@ -59,6 +59,16 @@ class ShortcutLauncher { mPackageName = packageName; } + @UserIdInt + public int getUserId() { + return mUserId; + } + + @NonNull + public String getPackageName() { + return mPackageName; + } + public void pinShortcuts(@NonNull ShortcutService s, @NonNull String packageName, @NonNull List ids) { final int idSize = ids.size(); @@ -103,7 +113,7 @@ class ShortcutLauncher { /** * Persist. */ - public void saveToXml(XmlSerializer out) throws IOException { + public void saveToXml(XmlSerializer out, boolean forBackup) throws IOException { final int size = mPinnedShortcuts.size(); if (size == 0) { return; // Nothing to write. @@ -190,7 +200,7 @@ class ShortcutLauncher { for (int j = 0; j < idSize; j++) { pw.print(prefix); - pw.print(" "); + pw.print(" Pinned: "); pw.print(ids.valueAt(j)); pw.println(); } diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index d6142510015e6..359ea1c956c58 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -41,7 +41,7 @@ import java.util.function.Predicate; /** * Package information used by {@link ShortcutService}. */ -class ShortcutPackage { +class ShortcutPackage implements ShortcutPackageItem { private static final String TAG = ShortcutService.TAG; static final String TAG_ROOT = "package"; @@ -64,10 +64,10 @@ class ShortcutPackage { private static final String ATTR_BITMAP_PATH = "bitmap-path"; @UserIdInt - final int mUserId; + private final int mUserId; @NonNull - final String mPackageName; + private final String mPackageName; /** * All the shortcuts from the package, keyed on IDs. @@ -94,6 +94,16 @@ class ShortcutPackage { mPackageName = packageName; } + @UserIdInt + public int getUserId() { + return mUserId; + } + + @NonNull + public String getPackageName() { + return mPackageName; + } + @Nullable public ShortcutInfo findShortcutById(String id) { return mShortcuts.get(id); @@ -381,7 +391,8 @@ class ShortcutPackage { pw.println(")"); } - public void saveToXml(@NonNull XmlSerializer out) throws IOException, XmlPullParserException { + public void saveToXml(@NonNull XmlSerializer out, boolean forBackup) + throws IOException, XmlPullParserException { final int size = mShortcuts.size(); if (size == 0 && mApiCallCount == 0) { @@ -396,14 +407,19 @@ class ShortcutPackage { ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime); for (int j = 0; j < size; j++) { - saveShortcut(out, mShortcuts.valueAt(j)); + saveShortcut(out, mShortcuts.valueAt(j), forBackup); } out.endTag(null, TAG_ROOT); } - private static void saveShortcut(XmlSerializer out, ShortcutInfo si) + private static void saveShortcut(XmlSerializer out, ShortcutInfo si, boolean forBackup) throws IOException, XmlPullParserException { + if (forBackup) { + if (!si.isPinned()) { + return; // Backup only pinned icons. + } + } out.startTag(null, TAG_SHORTCUT); ShortcutService.writeAttr(out, ATTR_ID, si.getId()); // writeAttr(out, "package", si.getPackageName()); // not needed @@ -414,9 +430,17 @@ class ShortcutPackage { ShortcutService.writeAttr(out, ATTR_WEIGHT, si.getWeight()); ShortcutService.writeAttr(out, ATTR_TIMESTAMP, si.getLastChangedTimestamp()); - ShortcutService.writeAttr(out, ATTR_FLAGS, si.getFlags()); - ShortcutService.writeAttr(out, ATTR_ICON_RES, si.getIconResourceId()); - ShortcutService.writeAttr(out, ATTR_BITMAP_PATH, si.getBitmapPath()); + if (forBackup) { + // Don't write icon information. Also drop the dynamic flag. + ShortcutService.writeAttr(out, ATTR_FLAGS, + si.getFlags() & + ~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES + | ShortcutInfo.FLAG_DYNAMIC)); + } else { + ShortcutService.writeAttr(out, ATTR_FLAGS, si.getFlags()); + ShortcutService.writeAttr(out, ATTR_ICON_RES, si.getIconResourceId()); + ShortcutService.writeAttr(out, ATTR_BITMAP_PATH, si.getBitmapPath()); + } ShortcutService.writeTagExtra(out, TAG_INTENT_EXTRAS, si.getIntentPersistableExtras()); diff --git a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java new file mode 100644 index 0000000000000..00ea6793f4bc0 --- /dev/null +++ b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java @@ -0,0 +1,292 @@ +/* + * 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.annotation.NonNull; +import android.annotation.UserIdInt; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.Signature; +import android.util.Slog; + +import com.android.internal.util.Preconditions; + +import libcore.io.Base64; +import libcore.util.HexEncoding; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.io.PrintWriter; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Package information used by {@link android.content.pm.ShortcutManager} for backup / restore. + * + * TODO: The methods about signature hashes are copied from BackupManagerService, which is not + * visible here. Unify the code. + */ +class ShortcutPackageInfo implements ShortcutPackageItem { + private static final String TAG = ShortcutService.TAG; + + static final String TAG_ROOT = "package-info"; + private static final String ATTR_NAME = "name"; + private static final String ATTR_VERSION = "version"; + private static final String ATTR_SHADOW = "shadow"; + + private static final String TAG_SIGNATURE = "signature"; + private static final String ATTR_SIGNATURE_HASH = "hash"; + + public interface ShortcutPackageInfoHolder { + ShortcutPackageInfo getShortcutPackageInfo(); + } + + private final String mPackageName; + + /** + * When true, this package information was restored from the previous device, and the app hasn't + * been installed yet. + */ + private boolean mIsShadow; + private int mVersionCode; + private ArrayList mSigHashes; + + private ShortcutPackageInfo(String packageName, int versionCode, ArrayList sigHashes, + boolean isShadow) { + mVersionCode = versionCode; + mIsShadow = isShadow; + mSigHashes = sigHashes; + mPackageName = Preconditions.checkNotNull(packageName); + } + + @NonNull + public String getPackageName() { + return mPackageName; + } + + public boolean isShadow() { + return mIsShadow; + } + + public boolean isInstalled() { + return !mIsShadow; + } + + public void setShadow(boolean shadow) { + mIsShadow = shadow; + } + + public int getVersionCode() { + return mVersionCode; + } + + private static byte[] hashSignature(Signature sig) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + digest.update(sig.toByteArray()); + return digest.digest(); + } catch (NoSuchAlgorithmException e) { + Slog.w(TAG, "No SHA-256 algorithm found!"); + } + return null; + } + + private static ArrayList hashSignatureArray(Signature[] sigs) { + if (sigs == null) { + return null; + } + + ArrayList hashes = new ArrayList(sigs.length); + for (Signature s : sigs) { + hashes.add(hashSignature(s)); + } + return hashes; + } + + private static boolean signaturesMatch(ArrayList storedSigHashes, PackageInfo target) { + if (target == null) { + return false; + } + + // If the target resides on the system partition, we allow it to restore + // data from the like-named package in a restore set even if the signatures + // do not match. (Unlike general applications, those flashed to the system + // partition will be signed with the device's platform certificate, so on + // different phones the same system app will have different signatures.) + if ((target.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + return true; + } + + // Allow unsigned apps, but not signed on one device and unsigned on the other + // !!! TODO: is this the right policy? + Signature[] deviceSigs = target.signatures; + if ((storedSigHashes == null || storedSigHashes.size() == 0) + && (deviceSigs == null || deviceSigs.length == 0)) { + return true; + } + if (storedSigHashes == null || deviceSigs == null) { + return false; + } + + // !!! TODO: this demands that every stored signature match one + // that is present on device, and does not demand the converse. + // Is this this right policy? + final int nStored = storedSigHashes.size(); + final int nDevice = deviceSigs.length; + + // hash each on-device signature + ArrayList deviceHashes = new ArrayList(nDevice); + for (int i = 0; i < nDevice; i++) { + deviceHashes.add(hashSignature(deviceSigs[i])); + } + + // now ensure that each stored sig (hash) matches an on-device sig (hash) + for (int n = 0; n < nStored; n++) { + boolean match = false; + final byte[] storedHash = storedSigHashes.get(n); + for (int i = 0; i < nDevice; i++) { + if (Arrays.equals(storedHash, deviceHashes.get(i))) { + match = true; + break; + } + } + // match is false when no on-device sig matched one of the stored ones + if (!match) { + return false; + } + } + + return true; + } + + public boolean canRestoreTo(PackageInfo target) { + if (target.versionCode < mVersionCode) { + Slog.w(TAG, String.format("Package current version %d < backed up version %d", + target.versionCode, mVersionCode)); + return false; + } + if (!signaturesMatch(mSigHashes, target)) { + Slog.w(TAG, "Package signature mismtach"); + return false; + } + return true; + } + + public static ShortcutPackageInfo generateForInstalledPackage( + ShortcutService s, String packageName, @UserIdInt int userId) { + final PackageInfo pi = s.getPackageInfo(packageName, userId, /*signature=*/ true); + if (pi.signatures == null || pi.signatures.length == 0) { + Slog.e(TAG, "Can't get signatures: package=" + packageName); + return null; + } + final ShortcutPackageInfo ret = new ShortcutPackageInfo(packageName, pi.versionCode, + hashSignatureArray(pi.signatures), /* shadow=*/ false); + + return ret; + } + + public void refreshAndSave(ShortcutService s, @UserIdInt int userId) { + final PackageInfo pi = s.getPackageInfo(mPackageName, userId, /*getSignatures=*/ true); + if (pi == null) { + Slog.w(TAG, "Package not found: " + mPackageName); + return; + } + mVersionCode = pi.versionCode; + mSigHashes = hashSignatureArray(pi.signatures); + + s.scheduleSaveUser(userId); + } + + public void saveToXml(XmlSerializer out, boolean forBackup) + throws IOException, XmlPullParserException { + + out.startTag(null, TAG_ROOT); + + ShortcutService.writeAttr(out, ATTR_NAME, mPackageName); + ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode); + ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow); + + for (int i = 0; i < mSigHashes.size(); i++) { + out.startTag(null, TAG_SIGNATURE); + ShortcutService.writeAttr(out, ATTR_SIGNATURE_HASH, Base64.encode(mSigHashes.get(i))); + out.endTag(null, TAG_SIGNATURE); + } + out.endTag(null, TAG_ROOT); + } + + public static ShortcutPackageInfo loadFromXml(XmlPullParser parser) + throws IOException, XmlPullParserException { + + final String packageName = ShortcutService.parseStringAttribute(parser, ATTR_NAME); + final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION); + final boolean shadow = ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW); + + final ArrayList hashes = new ArrayList<>(); + + + final int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type != XmlPullParser.START_TAG) { + continue; + } + final int depth = parser.getDepth(); + final String tag = parser.getName(); + switch (tag) { + case TAG_SIGNATURE: { + final String hash = ShortcutService.parseStringAttribute( + parser, ATTR_SIGNATURE_HASH); + hashes.add(Base64.decode(hash.getBytes())); + continue; + } + } + throw ShortcutService.throwForInvalidTag(depth, tag); + } + return new ShortcutPackageInfo(packageName, versionCode, hashes, shadow); + } + + public void dump(ShortcutService s, PrintWriter pw, String prefix) { + pw.println(); + + pw.print(prefix); + pw.print("PackageInfo: "); + pw.print(mPackageName); + pw.println(); + + pw.print(prefix); + pw.print(" IsShadow: "); + pw.print(mIsShadow); + pw.println(); + + pw.print(prefix); + pw.print(" Version: "); + pw.print(mVersionCode); + pw.println(); + + for (int i = 0; i < mSigHashes.size(); i++) { + pw.print(prefix); + pw.print(" "); + pw.print("SigHash: "); + pw.println(HexEncoding.encode(mSigHashes.get(i))); + } + } +} diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java new file mode 100644 index 0000000000000..526c84db69637 --- /dev/null +++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java @@ -0,0 +1,31 @@ +/* + * 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.annotation.NonNull; + +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; + +public interface ShortcutPackageItem { + @NonNull + String getPackageName(); + + 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 42954f528a2bf..0b33adace0d44 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -19,13 +19,17 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; +import android.app.AppGlobals; import android.content.ComponentName; import android.content.ContentProvider; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; import android.content.pm.IShortcutService; import android.content.pm.LauncherApps; import android.content.pm.LauncherApps.ShortcutQuery; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; @@ -55,7 +59,6 @@ import android.os.ShellCommand; import android.os.UserHandle; import android.os.UserManager; import android.text.TextUtils; -import android.text.format.Formatter; import android.text.format.Time; import android.util.ArrayMap; import android.util.ArraySet; @@ -70,13 +73,13 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import com.android.internal.os.BackgroundThread; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.SystemService; import libcore.io.IoUtils; -import libcore.util.Objects; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -122,6 +125,7 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ -249,6 +253,7 @@ public class ShortcutService extends IShortcutService.Stub { private int mSaveDelayMillis; + private final IPackageManager mIPackageManager; private final PackageManagerInternal mPackageManagerInternal; private final UserManager mUserManager; @@ -264,6 +269,7 @@ public class ShortcutService extends IShortcutService.Stub { mContext = Preconditions.checkNotNull(context); LocalServices.addService(ShortcutServiceInternal.class, new LocalService()); mHandler = new Handler(looper); + mIPackageManager = AppGlobals.getPackageManager(); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mUserManager = context.getSystemService(UserManager.class); @@ -319,6 +325,8 @@ public class ShortcutService extends IShortcutService.Stub { synchronized (mLock) { // Preload getUserShortcutsLocked(userId); + + cleanupGonePackages(userId); } } @@ -434,6 +442,10 @@ public class ShortcutService extends IShortcutService.Stub { return parser.getAttributeValue(null, attribute); } + static boolean parseBooleanAttribute(XmlPullParser parser, String attribute) { + return parseLongAttribute(parser, attribute) == 1; + } + static int parseIntAttribute(XmlPullParser parser, String attribute) { return (int) parseLongAttribute(parser, attribute); } @@ -510,6 +522,12 @@ public class ShortcutService extends IShortcutService.Stub { writeAttr(out, name, String.valueOf(value)); } + static void writeAttr(XmlSerializer out, String name, boolean value) throws IOException { + if (value) { + writeAttr(out, name, "1"); + } + } + static void writeAttr(XmlSerializer out, String name, ComponentName comp) throws IOException { if (comp == null) return; writeAttr(out, name, comp.flattenToString()); @@ -616,7 +634,7 @@ public class ShortcutService extends IShortcutService.Stub { out.setOutput(outs, StandardCharsets.UTF_8.name()); out.startDocument(null, true); - getUserShortcutsLocked(userId).saveToXml(out); + getUserShortcutsLocked(userId).saveToXml(this, out, /* forBackup= */ false); out.endDocument(); @@ -682,17 +700,17 @@ public class ShortcutService extends IShortcutService.Stub { } private void scheduleSaveBaseState() { - scheduleSave(UserHandle.USER_NULL); // Special case -- use USER_NULL for base state. + scheduleSaveInner(UserHandle.USER_NULL); // Special case -- use USER_NULL for base state. } void scheduleSaveUser(@UserIdInt int userId) { - scheduleSave(userId); + scheduleSaveInner(userId); } // In order to re-schedule, we need to reuse the same instance, so keep it in final. private final Runnable mSaveDirtyInfoRunner = this::saveDirtyInfo; - private void scheduleSave(@UserIdInt int userId) { + private void scheduleSaveInner(@UserIdInt int userId) { if (DEBUG) { Slog.d(TAG, "Scheduling to save for " + userId); } @@ -1169,6 +1187,8 @@ public class ShortcutService extends IShortcutService.Stub { final int size = newShortcuts.size(); synchronized (mLock) { + getUserShortcutsLocked(userId).ensurePackageInfo(this, packageName, userId); + final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); // Throttling. @@ -1204,6 +1224,8 @@ public class ShortcutService extends IShortcutService.Stub { final int size = newShortcuts.size(); synchronized (mLock) { + getUserShortcutsLocked(userId).ensurePackageInfo(this, packageName, userId); + final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); // Throttling. @@ -1241,6 +1263,8 @@ public class ShortcutService extends IShortcutService.Stub { verifyCaller(packageName, userId); synchronized (mLock) { + getUserShortcutsLocked(userId).ensurePackageInfo(this, packageName, userId); + final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); // Throttling. @@ -1376,10 +1400,7 @@ public class ShortcutService extends IShortcutService.Stub { @VisibleForTesting boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) { synchronized (mLock) { - long start = 0; - if (DEBUG) { - start = System.currentTimeMillis(); - } + long start = System.currentTimeMillis(); final ShortcutUser user = getUserShortcutsLocked(userId); @@ -1430,8 +1451,8 @@ public class ShortcutService extends IShortcutService.Stub { lastPriority = ri.priority; } } + final long end = System.currentTimeMillis(); if (DEBUG) { - long end = System.currentTimeMillis(); Slog.v(TAG, String.format("hasShortcutPermission took %d ms", end - start)); } if (detected != null) { @@ -1473,6 +1494,9 @@ public class ShortcutService extends IShortcutService.Stub { mUser.getPackages().valueAt(i).refreshPinnedFlags(this); } + // Remove the package info too. + mUser.getPackageInfos().remove(packageName); + scheduleSaveUser(userId); if (doNotify) { @@ -1567,6 +1591,9 @@ public class ShortcutService extends IShortcutService.Stub { Preconditions.checkNotNull(shortcutIds, "shortcutIds"); synchronized (mLock) { + getUserShortcutsLocked(userId).ensurePackageInfo( + ShortcutService.this, callingPackage, userId); + getLauncherShortcuts(callingPackage, userId).pinShortcuts( ShortcutService.this, packageName, shortcutIds); } @@ -1640,7 +1667,16 @@ public class ShortcutService extends IShortcutService.Stub { } } - private PackageMonitor mPackageMonitor = new PackageMonitor() { + /** + * Package event callbacks. + */ + @VisibleForTesting + final PackageMonitor mPackageMonitor = new PackageMonitor() { + @Override + public void onPackageAdded(String packageName, int uid) { + handlePackageAdded(packageName, getChangingUserId()); + } + @Override public void onPackageUpdateFinished(String packageName, int uid) { handlePackageUpdateFinished(packageName, getChangingUserId()); @@ -1650,38 +1686,120 @@ public class ShortcutService extends IShortcutService.Stub { public void onPackageRemoved(String packageName, int uid) { handlePackageRemoved(packageName, getChangingUserId()); } - - @Override - public void onPackageRemovedAllUsers(String packageName, int uid) { - handlePackageRemovedAllUsers(packageName, getChangingUserId()); - } }; - void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) { + /** + * Called when a user is unlocked. Check all known packages still exist, and otherwise + * perform cleanup. + */ + private void cleanupGonePackages(@UserIdInt int userId) { if (DEBUG) { - Slog.d(TAG, "onPackageUpdateFinished() userId=" + userId); + Slog.d(TAG, "cleanupGonePackages() userId=" + userId); + } + ArrayList gonePackages = null; + + final ShortcutUser user = getUserShortcutsLocked(userId); + final ArrayMap infos = user.getPackageInfos(); + for (int i = infos.size() -1; i >= 0; i--) { + final ShortcutPackageInfo info = infos.valueAt(i); + if (info.isShadow()) { + continue; + } + if (isPackageInstalled(info.getPackageName(), userId)) { + continue; + } + gonePackages = ArrayUtils.add(gonePackages, info.getPackageName()); + } + if (gonePackages != null) { + for (int i = gonePackages.size() - 1; i >= 0; i--) { + handlePackageGone(gonePackages.get(i), userId); + } } - // TODO Update the version. } - void handlePackageRemoved(String packageName, @UserIdInt int userId) { + private void handlePackageAdded(String packageName, @UserIdInt int userId) { if (DEBUG) { - Slog.d(TAG, "onPackageRemoved() userId=" + userId); + Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId)); + } + synchronized (mLock) { + final ArrayMap infos = + getUserShortcutsLocked(userId).getPackageInfos(); + final ShortcutPackageInfo existing = infos.get(packageName); + + if (existing != null && existing.isShadow()) { + Slog.w(TAG, "handlePackageAdded: TODO Restore not implemented"); + } + } + } + + private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) { + final PackageInfo pi = getPackageInfo(packageName, userId); + if (pi == null) { + Slog.w(TAG, "Package not found: " + packageName); + return; + } + synchronized (mLock) { + final ShortcutPackageInfo spi = + getUserShortcutsLocked(userId).getPackageInfos().get(packageName); + if (spi != null) { + spi.refreshAndSave(this, userId); + } + } + } + + private void handlePackageRemoved(String packageName, @UserIdInt int userId) { + if (DEBUG) { + Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName, userId)); } synchronized (mLock) { cleanUpPackageLocked(packageName, userId); } } - void handlePackageRemovedAllUsers(String packageName, @UserIdInt int userId) { + private void handlePackageGone(String packageName, @UserIdInt int userId) { if (DEBUG) { - Slog.d(TAG, "onPackageRemovedAllUsers() userId=" + userId); + Slog.d(TAG, String.format("handlePackageGone: %s user=%d", packageName, userId)); } synchronized (mLock) { cleanUpPackageLocked(packageName, userId); } + } - // TODO Remove from all users, which we can't if the user is locked. + // === Backup & restore === + + PackageInfo getPackageInfo(String packageName, @UserIdInt int userId) { + return getPackageInfo(packageName, userId, /*getSignatures=*/ false); + } + + PackageInfo getPackageInfo(String packageName, @UserIdInt int userId, + boolean getSignatures) { + return injectPackageInfo(packageName, userId, getSignatures); + } + + @VisibleForTesting + PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId, + boolean getSignatures) { + try { + return mIPackageManager.getPackageInfo(packageName, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | (getSignatures ? PackageManager.GET_SIGNATURES : 0) + , userId); + } catch (RemoteException e) { + // Shouldn't happen. + Slog.wtf(TAG, "RemoteException", e); + return null; + } + } + + boolean shouldBackupApp(String packageName, int userId) { + final PackageInfo pi = getPackageInfo(packageName, userId); + return (pi != null) && + ((pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0); + } + + private boolean isPackageInstalled(String packageName, int userId) { + return getPackageInfo(packageName, userId) != null; } // === Dump === @@ -2039,4 +2157,14 @@ public class ShortcutService extends IShortcutService.Stub { return pkg.findShortcutById(shortcutId); } } + + @VisibleForTesting + ShortcutPackageInfo getPackageInfoForTest(String packageName, int userId) { + synchronized (mLock) { + final ShortcutUser user = mUsers.get(userId); + if (user == null) return null; + + return user.getPackageInfos().get(packageName); + } + } } diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java index 4a6b1e4d6bfc5..1a00cda788162 100644 --- a/services/core/java/com/android/server/pm/ShortcutUser.java +++ b/services/core/java/com/android/server/pm/ShortcutUser.java @@ -19,6 +19,7 @@ import android.annotation.NonNull; import android.annotation.UserIdInt; import android.content.ComponentName; import android.util.ArrayMap; +import android.util.Slog; import libcore.util.Objects; @@ -47,6 +48,8 @@ class ShortcutUser { private final ArrayMap mLaunchers = new ArrayMap<>(); + private final ArrayMap mPackageInfos = new ArrayMap<>(); + private ComponentName mLauncherComponent; public ShortcutUser(int userId) { @@ -61,6 +64,10 @@ class ShortcutUser { return mLaunchers; } + public ArrayMap getPackageInfos() { + return mPackageInfos; + } + public ShortcutPackage getPackageShortcuts(@NonNull String packageName) { ShortcutPackage ret = mPackages.get(packageName); if (ret == null) { @@ -79,25 +86,58 @@ class ShortcutUser { return ret; } - public void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException { + public void ensurePackageInfo(ShortcutService s, String packageName, @UserIdInt int userId) { + final ShortcutPackageInfo existing = mPackageInfos.get(packageName); + + if (existing != null) { + return; + } + if (ShortcutService.DEBUG) { + Slog.d(TAG, String.format("Fetching package info: %s user=%d", packageName, userId)); + } + final ShortcutPackageInfo newSpi = ShortcutPackageInfo.generateForInstalledPackage( + s, packageName, userId); + mPackageInfos.put(packageName, newSpi); + s.scheduleSaveUser(mUserId); + } + + public void saveToXml(ShortcutService s, XmlSerializer out, boolean forBackup) + throws IOException, XmlPullParserException { out.startTag(null, TAG_ROOT); ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLauncherComponent); - final int lsize = mLaunchers.size(); - for (int i = 0; i < lsize; i++) { - mLaunchers.valueAt(i).saveToXml(out); + { + final int size = mPackageInfos.size(); + for (int i = 0; i < size; i++) { + saveShortcutPackageItem(s, out, mPackageInfos.valueAt(i), forBackup); + } } - - final int psize = mPackages.size(); - for (int i = 0; i < psize; i++) { - mPackages.valueAt(i).saveToXml(out); + { + final int size = mLaunchers.size(); + for (int i = 0; i < size; i++) { + saveShortcutPackageItem(s, out, mLaunchers.valueAt(i), forBackup); + } + } + { + final int size = mPackages.size(); + for (int i = 0; i < size; i++) { + saveShortcutPackageItem(s, out, mPackages.valueAt(i), forBackup); + } } out.endTag(null, TAG_ROOT); } + private void saveShortcutPackageItem(ShortcutService s, XmlSerializer out, + ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException { + if (forBackup && !s.shouldBackupApp(spi.getPackageName(), mUserId)) { + return; // Don't save. + } + spi.saveToXml(out, forBackup); + } + public static ShortcutUser loadFromXml(XmlPullParser parser, int userId) throws IOException, XmlPullParserException { final ShortcutUser ret = new ShortcutUser(userId); @@ -121,7 +161,7 @@ class ShortcutUser { final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(parser, userId); // Don't use addShortcut(), we don't need to save the icon. - ret.getPackages().put(shortcuts.mPackageName, shortcuts); + ret.getPackages().put(shortcuts.getPackageName(), shortcuts); continue; } @@ -129,7 +169,15 @@ class ShortcutUser { final ShortcutLauncher shortcuts = ShortcutLauncher.loadFromXml(parser, userId); - ret.getLaunchers().put(shortcuts.mPackageName, shortcuts); + ret.getLaunchers().put(shortcuts.getPackageName(), shortcuts); + continue; + } + + case ShortcutPackageInfo.TAG_ROOT: { + final ShortcutPackageInfo pi = + ShortcutPackageInfo.loadFromXml(parser); + + ret.getPackageInfos().put(pi.getPackageName(), pi); continue; } } @@ -175,5 +223,9 @@ class ShortcutUser { for (int i = 0; i < mPackages.size(); i++) { mPackages.valueAt(i).dump(s, pw, prefix + " "); } + + for (int i = 0; i < mPackageInfos.size(); i++) { + mPackageInfos.valueAt(i).dump(s, pw, prefix + " "); + } } } 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 28966ca5a7227..a30d2b5caeef5 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java @@ -32,19 +32,23 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.ILauncherApps; import android.content.pm.LauncherApps; import android.content.pm.LauncherApps.ShortcutQuery; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.content.pm.ShortcutServiceInternal; +import android.content.pm.Signature; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; import android.graphics.drawable.Icon; +import android.net.Uri; import android.os.BaseBundle; import android.os.Bundle; import android.os.FileUtils; @@ -103,7 +107,6 @@ import java.util.Set; * TODO: separate, detailed tests for ShortcutInfo (CTS?) * * * TODO: Cross-user test (do in CTS?) - * */ @SmallTest public class ShortcutManagerTest extends InstrumentationTestCase { @@ -217,8 +220,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { @Override int injectGetPackageUid(String packageName, int userId) { - Integer uid = mInjectedPackageUidMap.get(packageName); - return UserHandle.getUid(getCallingUserId(), (uid != null ? uid : 0)); + return getInjectedPackageInfo(packageName, userId, false).applicationInfo.uid; } @Override @@ -252,6 +254,12 @@ public class ShortcutManagerTest extends InstrumentationTestCase { return LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage); } + @Override + PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId, + boolean getSignatures) { + return getInjectedPackageInfo(packageName, userId, getSignatures); + } + @Override void postToHandler(Runnable r) { final long token = mContext.injectClearCallingIdentity(); @@ -355,7 +363,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { private int mInjectedCallingUid; private String mInjectedClientPackage; - private Map mInjectedPackageUidMap; + private Map mInjectedPackages; private PackageManager mMockPackageManager; private PackageManagerInternal mMockPackageManagerInternal; @@ -407,12 +415,12 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mInjectedCurrentTimeLillis = START_TIME; - mInjectedPackageUidMap = new HashMap<>(); - mInjectedPackageUidMap.put(CALLING_PACKAGE_1, CALLING_UID_1); - mInjectedPackageUidMap.put(CALLING_PACKAGE_2, CALLING_UID_2); - mInjectedPackageUidMap.put(CALLING_PACKAGE_3, CALLING_UID_3); - mInjectedPackageUidMap.put(LAUNCHER_1, LAUNCHER_UID_1); - mInjectedPackageUidMap.put(LAUNCHER_2, LAUNCHER_UID_2); + mInjectedPackages = new HashMap<>();; + 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(LAUNCHER_1, LAUNCHER_UID_1, 4); + addPackage(LAUNCHER_2, LAUNCHER_UID_2, 5); mInjectedFilePathRoot = new File(getTestContext().getCacheDir(), "test-files"); @@ -448,12 +456,57 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mService.onBootPhase(SystemService.PHASE_LOCK_SETTINGS_READY); } + private void addPackage(String packageName, int uid, int version) { + addPackage(packageName, uid, version, packageName); + } + + private Signature[] genSignatures(String... signatures) { + final Signature[] sigs = new Signature[signatures.length]; + for (int i = 0; i < signatures.length; i++){ + sigs[i] = new Signature(signatures[i].getBytes()); + } + return sigs; + } + + private PackageInfo genPackage(String packageName, int uid, int version, String... signatures) { + final PackageInfo pi = new PackageInfo(); + pi.packageName = packageName; + pi.applicationInfo = new ApplicationInfo(); + pi.applicationInfo.uid = uid; + pi.versionCode = version; + pi.signatures = genSignatures(signatures); + + return pi; + } + + private void addPackage(String packageName, int uid, int version, String... signatures) { + mInjectedPackages.put(packageName, genPackage(packageName, uid, version, signatures)); + } + + PackageInfo getInjectedPackageInfo(String packageName, @UserIdInt int userId, + boolean getSignatures) { + final PackageInfo pi = mInjectedPackages.get(packageName); + if (pi == null) return null; + + final PackageInfo ret = new PackageInfo(); + ret.packageName = pi.packageName; + ret.versionCode = pi.versionCode; + ret.applicationInfo = new ApplicationInfo(pi.applicationInfo); + ret.applicationInfo.uid = UserHandle.getUid(userId, pi.applicationInfo.uid); + + if (getSignatures) { + ret.signatures = pi.signatures; + } + + return ret; + } + /** Replace the current calling package */ private void setCaller(String packageName, int userId) { mInjectedClientPackage = packageName; - mInjectedCallingUid = UserHandle.getUid(userId, - Preconditions.checkNotNull(mInjectedPackageUidMap.get(packageName), - "Unknown package")); + mInjectedCallingUid = + Preconditions.checkNotNull(getInjectedPackageInfo(packageName, userId, false), + "Unknown package").applicationInfo.uid; } private void setCaller(String packageName) { @@ -466,13 +519,13 @@ public class ShortcutManagerTest extends InstrumentationTestCase { private void runWithCaller(String packageName, int userId, Runnable r) { final String previousPackage = mInjectedClientPackage; - final int previousUid = mInjectedCallingUid; + final int previousUserId = UserHandle.getUserId(mInjectedCallingUid); setCaller(packageName, userId); r.run(); - setCaller(previousPackage, previousUid); + setCaller(previousPackage, previousUserId); } private int getCallingUserId() { @@ -852,6 +905,18 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertTrue(b == null || b.size() == 0); } + private void assertShortcutPackageInfo(String packageName, int userId, int expectedVersion) { + ShortcutPackageInfo spi = mService.getPackageInfoForTest(packageName, userId); + assertNotNull(spi); + assertEquals(expectedVersion, spi.getVersionCode()); + + assertTrue(spi.canRestoreTo(genPackage(packageName, /*uid*/ 0, 9999999, packageName))); + } + + private void assertNoShortcutPackageInfo(String packageName, int userId) { + assertNull(mService.getPackageInfoForTest(packageName, userId)); + } + private ShortcutInfo getPackageShortcut(String packageName, String shortcutId, int userId) { return mService.getPackageShortcutForTest(packageName, shortcutId, userId); } @@ -886,6 +951,22 @@ public class ShortcutManagerTest extends InstrumentationTestCase { return getLauncherShortcuts(launcher, userId, ShortcutQuery.FLAG_GET_PINNED); } + + private Intent genPackageDeleteIntent(String pakcageName, int userId) { + Intent i = new Intent(Intent.ACTION_PACKAGE_REMOVED); + i.setData(Uri.parse("package:" + pakcageName)); + i.putExtra(Intent.EXTRA_USER_HANDLE, userId); + return i; + } + + private Intent genPackageUpdateIntent(String pakcageName, int userId) { + Intent i = new Intent(Intent.ACTION_PACKAGE_ADDED); + i.setData(Uri.parse("package:" + pakcageName)); + i.putExtra(Intent.EXTRA_USER_HANDLE, userId); + i.putExtra(Intent.EXTRA_REPLACING, true); + return i; + } + /** * Wrap a set in an ArraySet just to get a better toString. */ @@ -1017,6 +1098,8 @@ public class ShortcutManagerTest extends InstrumentationTestCase { } public void testSetDynamicShortcuts() { + setCaller(CALLING_PACKAGE_1, USER_0); + final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.icon1); final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource( getTestContext().getResources(), R.drawable.icon2)); @@ -1045,6 +1128,11 @@ public class ShortcutManagerTest extends InstrumentationTestCase { "shortcut1", "shortcut2"); assertEquals(2, mManager.getRemainingCallCount()); + assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1); + assertNoShortcutPackageInfo(CALLING_PACKAGE_2, USER_0); + assertNoShortcutPackageInfo(CALLING_PACKAGE_1, USER_10); + assertNoShortcutPackageInfo(CALLING_PACKAGE_2, USER_10); + // TODO: Check fields assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1))); @@ -1068,9 +1156,20 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertEquals(2, mManager.getDynamicShortcuts().size()); // TODO Check max number + + runWithCaller(CALLING_PACKAGE_2, USER_10, () -> { + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(makeShortcut("s1")))); + + assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1); + assertNoShortcutPackageInfo(CALLING_PACKAGE_2, USER_0); + assertNoShortcutPackageInfo(CALLING_PACKAGE_1, USER_10); + assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_10, 2); + }); } public void testAddDynamicShortcuts() { + setCaller(CALLING_PACKAGE_1, USER_0); + final ShortcutInfo si1 = makeShortcut("shortcut1"); final ShortcutInfo si2 = makeShortcut("shortcut2"); final ShortcutInfo si3 = makeShortcut("shortcut3"); @@ -1099,6 +1198,11 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // TODO Check max number // TODO Check fields. + + runWithCaller(CALLING_PACKAGE_2, USER_10, () -> { + assertTrue(mManager.addDynamicShortcut(makeShortcut("s1"))); + assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_10, 2); + }); } public void testDeleteDynamicShortcut() { @@ -1691,6 +1795,15 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // TODO Check with other fields too. // TODO Check bitmap removal too. + + runWithCaller(CALLING_PACKAGE_2, USER_11, () -> { + assertNoShortcutPackageInfo(CALLING_PACKAGE_2, USER_11); + + mManager.updateShortcuts(Arrays.asList()); + + // Even an empty update call will populate the package info. + assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_11, 2); + }); } // TODO: updateShortcuts() @@ -1886,9 +1999,16 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // Pin some. runWithCaller(LAUNCHER_1, USER_0, () -> { + assertNoShortcutPackageInfo(LAUNCHER_1, USER_0); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, Arrays.asList("s2", "s3"), getCallingUser()); + assertShortcutPackageInfo(LAUNCHER_1, USER_0, 4); + assertNoShortcutPackageInfo(LAUNCHER_2, USER_0); + assertNoShortcutPackageInfo(LAUNCHER_1, USER_10); + assertNoShortcutPackageInfo(LAUNCHER_2, USER_10); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, Arrays.asList("s3", "s4", "s5"), getCallingUser()); @@ -1949,11 +2069,19 @@ public class ShortcutManagerTest extends InstrumentationTestCase { dumpsysOnLogcat(); + assertNoShortcutPackageInfo(LAUNCHER_1, USER_0); + assertNoShortcutPackageInfo(LAUNCHER_2, USER_0); + assertNoShortcutPackageInfo(LAUNCHER_1, USER_10); + assertNoShortcutPackageInfo(LAUNCHER_2, USER_10); + // Pin some. runWithCaller(LAUNCHER_1, USER_0, () -> { mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, Arrays.asList("s3", "s4"), getCallingUser()); + assertShortcutPackageInfo(LAUNCHER_1, USER_0, 4); + assertNoShortcutPackageInfo(LAUNCHER_2, USER_0); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, Arrays.asList("s1", "s2", "s4"), getCallingUser()); }); @@ -2027,10 +2155,16 @@ public class ShortcutManagerTest extends InstrumentationTestCase { | ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())), "s2"); + assertNoShortcutPackageInfo(LAUNCHER_2, USER_0); + assertNoShortcutPackageInfo(LAUNCHER_2, USER_10); + // Now pin some. mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, Arrays.asList("s1", "s2"), getCallingUser()); + assertShortcutPackageInfo(LAUNCHER_2, USER_0, 5); + assertNoShortcutPackageInfo(LAUNCHER_2, USER_10); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, Arrays.asList("s1", "s2"), getCallingUser()); @@ -2048,10 +2182,26 @@ public class ShortcutManagerTest extends InstrumentationTestCase { "s2"); }); + assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1); + assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_0, 2); + assertNoShortcutPackageInfo(CALLING_PACKAGE_3, USER_0); + assertShortcutPackageInfo(LAUNCHER_1, USER_0, 4); + assertShortcutPackageInfo(LAUNCHER_2, USER_0, 5); + // Re-initialize and load from the files. mService.saveDirtyInfo(); initService(); + // Load from file. + mService.handleUnlockUser(USER_0); + + // Make sure package info is restored too. + assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1); + assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_0, 2); + assertNoShortcutPackageInfo(CALLING_PACKAGE_3, USER_0); + assertShortcutPackageInfo(LAUNCHER_1, USER_0, 4); + assertShortcutPackageInfo(LAUNCHER_2, USER_0, 5); + runWithCaller(LAUNCHER_1, USER_0, () -> { assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly( mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1, @@ -2489,6 +2639,10 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mService.getShortcutsForTest().get(UserHandle.USER_SYSTEM).setLauncherComponent( mService, new ComponentName("pkg1", "class")); + assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1); + assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_0, 2); + assertNoShortcutPackageInfo(CALLING_PACKAGE_3, USER_0); + // Restore. mService.saveDirtyInfo(); initService(); @@ -2499,6 +2653,10 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // this will pre-load the per-user info. mService.handleUnlockUser(UserHandle.USER_SYSTEM); + assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1); + assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_0, 2); + assertNoShortcutPackageInfo(CALLING_PACKAGE_3, USER_0); + // Now it's loaded. assertEquals(1, mService.getShortcutsForTest().size()); @@ -2637,6 +2795,11 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10); assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10)); + mService.saveDirtyInfo(); // Nonexistent package. @@ -2664,6 +2827,11 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10); assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10)); + mService.saveDirtyInfo(); // Remove a package. @@ -2690,6 +2858,11 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10); assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10); + assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10)); + mService.saveDirtyInfo(); // Remove a launcher. @@ -2738,6 +2911,11 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10); assertShortcutNotExists(CALLING_PACKAGE_2, "s10_2", USER_10); + assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); + assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10)); + mService.saveDirtyInfo(); // Remove the other launcher from user 10 too. @@ -2792,4 +2970,162 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // 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( + "dummy", /* uid */ 0, version, signatures))); + } + + public void testCanRestoreTo() { + addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sig1"); + addPackage(CALLING_PACKAGE_2, CALLING_UID_1, 10, "sig1", "sig2"); + + final ShortcutPackageInfo spi1 = ShortcutPackageInfo.generateForInstalledPackage( + mService, CALLING_PACKAGE_1, USER_0); + final ShortcutPackageInfo spi2 = ShortcutPackageInfo.generateForInstalledPackage( + mService, CALLING_PACKAGE_2, USER_0); + + checkCanRestoreTo(true, spi1, 10, "sig1"); + checkCanRestoreTo(true, spi1, 10, "x", "sig1"); + checkCanRestoreTo(true, spi1, 10, "sig1", "y"); + checkCanRestoreTo(true, spi1, 10, "x", "sig1", "y"); + checkCanRestoreTo(true, spi1, 11, "sig1"); + + checkCanRestoreTo(false, spi1, 10 /* empty */); + checkCanRestoreTo(false, spi1, 10, "x"); + checkCanRestoreTo(false, spi1, 10, "x", "y"); + checkCanRestoreTo(false, spi1, 10, "x"); + checkCanRestoreTo(false, spi1, 9, "sig1"); + + checkCanRestoreTo(true, spi2, 10, "sig1", "sig2"); + checkCanRestoreTo(true, spi2, 10, "sig2", "sig1"); + checkCanRestoreTo(true, spi2, 10, "x", "sig1", "sig2"); + checkCanRestoreTo(true, spi2, 10, "x", "sig2", "sig1"); + checkCanRestoreTo(true, spi2, 10, "sig1", "sig2", "y"); + checkCanRestoreTo(true, spi2, 10, "sig2", "sig1", "y"); + checkCanRestoreTo(true, spi2, 10, "x", "sig1", "sig2", "y"); + checkCanRestoreTo(true, spi2, 10, "x", "sig2", "sig1", "y"); + checkCanRestoreTo(true, spi2, 11, "x", "sig2", "sig1", "y"); + + checkCanRestoreTo(false, spi2, 10, "sig1", "sig2x"); + checkCanRestoreTo(false, spi2, 10, "sig2", "sig1x"); + checkCanRestoreTo(false, spi2, 10, "x", "sig1x", "sig2"); + checkCanRestoreTo(false, spi2, 10, "x", "sig2x", "sig1"); + checkCanRestoreTo(false, spi2, 10, "sig1", "sig2x", "y"); + checkCanRestoreTo(false, spi2, 10, "sig2", "sig1x", "y"); + checkCanRestoreTo(false, spi2, 10, "x", "sig1x", "sig2", "y"); + checkCanRestoreTo(false, spi2, 10, "x", "sig2x", "sig1", "y"); + checkCanRestoreTo(false, spi2, 11, "x", "sig2x", "sig1", "y"); + } + + public void testShortcutPackageInfoRefresh() { + addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sig1"); + + final ShortcutPackageInfo spi1 = ShortcutPackageInfo.generateForInstalledPackage( + mService, CALLING_PACKAGE_1, USER_0); + + checkCanRestoreTo(true, spi1, 10, "sig1"); + + addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 11, "sig1", "sig2"); + + spi1.refreshAndSave(mService, USER_0); + + mService.handleCleanupUser(USER_0); + initService(); + + checkCanRestoreTo(false, spi1, 10, "sig1", "sig2"); + checkCanRestoreTo(false, spi1, 11, "sig", "sig2"); + checkCanRestoreTo(false, spi1, 11, "sig1", "sig"); + checkCanRestoreTo(true, spi1, 11, "sig1", "sig2"); + } + + public void testHandlePackageDelete() { + setCaller(CALLING_PACKAGE_1, USER_0); + assertTrue(mManager.addDynamicShortcut(makeShortcut("s1"))); + + setCaller(CALLING_PACKAGE_2, USER_0); + assertTrue(mManager.addDynamicShortcut(makeShortcut("s1"))); + + setCaller(CALLING_PACKAGE_3, USER_0); + assertTrue(mManager.addDynamicShortcut(makeShortcut("s1"))); + + setCaller(CALLING_PACKAGE_1, USER_10); + assertTrue(mManager.addDynamicShortcut(makeShortcut("s1"))); + + setCaller(CALLING_PACKAGE_2, USER_10); + assertTrue(mManager.addDynamicShortcut(makeShortcut("s1"))); + + setCaller(CALLING_PACKAGE_3, USER_10); + assertTrue(mManager.addDynamicShortcut(makeShortcut("s1"))); + + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_0)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_10)); + + mService.mPackageMonitor.onReceive(getTestContext(), + genPackageDeleteIntent(CALLING_PACKAGE_1, USER_0)); + + assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_0)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_10)); + + mService.mPackageMonitor.onReceive(getTestContext(), + genPackageDeleteIntent(CALLING_PACKAGE_2, USER_10)); + + assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_0)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); + assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_10)); + + mInjectedPackages.remove(CALLING_PACKAGE_1); + mInjectedPackages.remove(CALLING_PACKAGE_3); + + mService.handleUnlockUser(USER_0); + assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); + assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_0)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); + assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_10)); + + mService.handleUnlockUser(USER_10); + assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0)); + assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_0)); + assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10)); + assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10)); + assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_10)); + } + + public void testHandlePackageUpdate() { + setCaller(CALLING_PACKAGE_1, USER_0); + assertTrue(mManager.addDynamicShortcut(makeShortcut("s1"))); + + assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)); + assertEquals(1, mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0).getVersionCode()); + + addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 123); + + mService.mPackageMonitor.onReceive(getTestContext(), + genPackageUpdateIntent("abc", USER_0)); + assertEquals(1, mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0).getVersionCode()); + + mService.mPackageMonitor.onReceive(getTestContext(), + genPackageUpdateIntent("abc", USER_10)); + assertEquals(1, mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0).getVersionCode()); + + mService.mPackageMonitor.onReceive(getTestContext(), + genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0)); + assertEquals(123, mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0) + .getVersionCode()); + } }