ShortcutManager: Handle package broadcasts.
- Do a cleanup when an app is gone.
- Record the app version and signatures.
- Also make saveToXml() capable of saving for backup.
Bug 27548047
Change-Id: I7eb2bbec7665b4d625630e7312c0f2a8b03c5ffa
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 0000000..a0fbc37
--- /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 f1920c7..740a8f7 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 @@
/**
* 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 @@
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 @@
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<String> ids) {
final int idSize = ids.size();
@@ -103,7 +113,7 @@
/**
* 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 @@
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 d614251..359ea1c 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 @@
/**
* 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 @@
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 @@
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 @@
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 @@
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 @@
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 0000000..00ea679
--- /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<byte[]> mSigHashes;
+
+ private ShortcutPackageInfo(String packageName, int versionCode, ArrayList<byte[]> 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<byte[]> hashSignatureArray(Signature[] sigs) {
+ if (sigs == null) {
+ return null;
+ }
+
+ ArrayList<byte[]> hashes = new ArrayList<byte[]>(sigs.length);
+ for (Signature s : sigs) {
+ hashes.add(hashSignature(s));
+ }
+ return hashes;
+ }
+
+ private static boolean signaturesMatch(ArrayList<byte[]> 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<byte[]> deviceHashes = new ArrayList<byte[]>(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<byte[]> 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 0000000..526c84d
--- /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 42954f5..0b33ada 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.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.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.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 @@
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 @@
private int mSaveDelayMillis;
+ private final IPackageManager mIPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
private final UserManager mUserManager;
@@ -264,6 +269,7 @@
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 @@
synchronized (mLock) {
// Preload
getUserShortcutsLocked(userId);
+
+ cleanupGonePackages(userId);
}
}
@@ -434,6 +442,10 @@
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 @@
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 @@
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 @@
}
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 @@
final int size = newShortcuts.size();
synchronized (mLock) {
+ getUserShortcutsLocked(userId).ensurePackageInfo(this, packageName, userId);
+
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
// Throttling.
@@ -1204,6 +1224,8 @@
final int size = newShortcuts.size();
synchronized (mLock) {
+ getUserShortcutsLocked(userId).ensurePackageInfo(this, packageName, userId);
+
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
// Throttling.
@@ -1241,6 +1263,8 @@
verifyCaller(packageName, userId);
synchronized (mLock) {
+ getUserShortcutsLocked(userId).ensurePackageInfo(this, packageName, userId);
+
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
// Throttling.
@@ -1376,10 +1400,7 @@
@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 @@
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 @@
mUser.getPackages().valueAt(i).refreshPinnedFlags(this);
}
+ // Remove the package info too.
+ mUser.getPackageInfos().remove(packageName);
+
scheduleSaveUser(userId);
if (doNotify) {
@@ -1567,6 +1591,9 @@
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 @@
}
}
- 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 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);
}
- // TODO Update the version.
+ ArrayList<String> gonePackages = null;
+
+ final ShortcutUser user = getUserShortcutsLocked(userId);
+ final ArrayMap<String, ShortcutPackageInfo> 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);
+ }
+ }
}
- 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<String, ShortcutPackageInfo> 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 @@
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 4a6b1e4..1a00cda 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.UserIdInt;
import android.content.ComponentName;
import android.util.ArrayMap;
+import android.util.Slog;
import libcore.util.Objects;
@@ -47,6 +48,8 @@
private final ArrayMap<String, ShortcutLauncher> mLaunchers = new ArrayMap<>();
+ private final ArrayMap<String, ShortcutPackageInfo> mPackageInfos = new ArrayMap<>();
+
private ComponentName mLauncherComponent;
public ShortcutUser(int userId) {
@@ -61,6 +64,10 @@
return mLaunchers;
}
+ public ArrayMap<String, ShortcutPackageInfo> getPackageInfos() {
+ return mPackageInfos;
+ }
+
public ShortcutPackage getPackageShortcuts(@NonNull String packageName) {
ShortcutPackage ret = mPackages.get(packageName);
if (ret == null) {
@@ -79,25 +86,58 @@
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 @@
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 @@
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 @@
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 28966ca..a30d2b5 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.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 @@
* TODO: separate, detailed tests for ShortcutInfo (CTS?) *
*
* TODO: Cross-user test (do in CTS?)
- *
*/
@SmallTest
public class ShortcutManagerTest extends InstrumentationTestCase {
@@ -217,8 +220,7 @@
@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
@@ -253,6 +255,12 @@
}
@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();
super.postToHandler(r);
@@ -355,7 +363,7 @@
private int mInjectedCallingUid;
private String mInjectedClientPackage;
- private Map<String, Integer> mInjectedPackageUidMap;
+ private Map<String, PackageInfo> mInjectedPackages;
private PackageManager mMockPackageManager;
private PackageManagerInternal mMockPackageManagerInternal;
@@ -407,12 +415,12 @@
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 @@
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 @@
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 @@
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 @@
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 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 @@
"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 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
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 @@
| 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 @@
"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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
// 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());
+ }
}