@GuardedBy("mLock")
private Exception mLastWtfStacktrace;
+ static class InvalidFileFormatException extends Exception {
+ public InvalidFileFormatException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
public ShortcutService(Context context) {
this(context, BackgroundThread.get().getLooper(), /*onyForPackgeManagerApis*/ false);
}
try {
final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false);
return ret;
- } catch (IOException | XmlPullParserException e) {
+ } catch (IOException | XmlPullParserException | InvalidFileFormatException e) {
Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
return null;
} finally {
}
private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is,
- boolean fromBackup) throws XmlPullParserException, IOException {
+ boolean fromBackup) throws XmlPullParserException, IOException,
+ InvalidFileFormatException {
final BufferedInputStream bis = new BufferedInputStream(is);
wtf("Can't restore: user " + userId + " is locked or not running");
return;
}
- final ShortcutUser user;
+ // Actually do restore.
+ final ShortcutUser restored;
final ByteArrayInputStream is = new ByteArrayInputStream(payload);
try {
- user = loadUserInternal(userId, is, /* fromBackup */ true);
- } catch (XmlPullParserException | IOException e) {
+ restored = loadUserInternal(userId, is, /* fromBackup */ true);
+ } catch (XmlPullParserException | IOException | InvalidFileFormatException e) {
Slog.w(TAG, "Restoration failed.", e);
return;
}
- mUsers.put(userId, user);
+ getUserShortcutsLocked(userId).mergeRestoredFile(restored);
// Rescan all packages to re-publish manifest shortcuts and do other checks.
rescanUpdatedPackagesLocked(userId,
import android.text.TextUtils;
import android.text.format.Formatter;
import android.util.ArrayMap;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import com.android.server.pm.ShortcutService.InvalidFileFormatException;
import libcore.util.Objects;
return mPackages.containsKey(packageName);
}
+ private void addPackage(@NonNull ShortcutPackage p) {
+ p.replaceUser(this);
+ mPackages.put(p.getPackageName(), p);
+ }
+
public ShortcutPackage removePackage(@NonNull String packageName) {
final ShortcutPackage removed = mPackages.remove(packageName);
return mLaunchers;
}
- public void addLauncher(ShortcutLauncher launcher) {
+ private void addLauncher(ShortcutLauncher launcher) {
+ launcher.replaceUser(this);
mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(),
launcher.getPackageName()), launcher);
}
throws IOException, XmlPullParserException {
out.startTag(null, TAG_ROOT);
- ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales);
- ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME,
- mLastAppScanTime);
- ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_OS_FINGERPRINT,
- mLastAppScanOsFingerprint);
+ if (!forBackup) {
+ // Don't have to back them up.
+ ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales);
+ ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME,
+ mLastAppScanTime);
+ ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_OS_FINGERPRINT,
+ mLastAppScanOsFingerprint);
- ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLastKnownLauncher);
+ ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLastKnownLauncher);
+ }
// Can't use forEachPackageItem due to the checked exceptions.
{
}
public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId,
- boolean fromBackup) throws IOException, XmlPullParserException {
+ boolean fromBackup) throws IOException, XmlPullParserException, InvalidFileFormatException {
final ShortcutUser ret = new ShortcutUser(s, userId);
- ret.mKnownLocales = ShortcutService.parseStringAttribute(parser,
- ATTR_KNOWN_LOCALES);
-
- // If lastAppScanTime is in the future, that means the clock went backwards.
- // Just scan all apps again.
- final long lastAppScanTime = ShortcutService.parseLongAttribute(parser,
- ATTR_LAST_APP_SCAN_TIME);
- final long currentTime = s.injectCurrentTimeMillis();
- ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0;
- ret.mLastAppScanOsFingerprint = ShortcutService.parseStringAttribute(parser,
- ATTR_LAST_APP_SCAN_OS_FINGERPRINT);
- 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();
-
- if (depth == outerDepth + 1) {
- switch (tag) {
- case TAG_LAUNCHER: {
- ret.mLastKnownLauncher = ShortcutService.parseComponentNameAttribute(
- parser, ATTR_VALUE);
- continue;
- }
- case ShortcutPackage.TAG_ROOT: {
- final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(
- s, ret, parser, fromBackup);
-
- // Don't use addShortcut(), we don't need to save the icon.
- ret.mPackages.put(shortcuts.getPackageName(), shortcuts);
- continue;
- }
-
- case ShortcutLauncher.TAG_ROOT: {
- ret.addLauncher(
- ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup));
- continue;
+ try {
+ ret.mKnownLocales = ShortcutService.parseStringAttribute(parser,
+ ATTR_KNOWN_LOCALES);
+
+ // If lastAppScanTime is in the future, that means the clock went backwards.
+ // Just scan all apps again.
+ final long lastAppScanTime = ShortcutService.parseLongAttribute(parser,
+ ATTR_LAST_APP_SCAN_TIME);
+ final long currentTime = s.injectCurrentTimeMillis();
+ ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0;
+ ret.mLastAppScanOsFingerprint = ShortcutService.parseStringAttribute(parser,
+ ATTR_LAST_APP_SCAN_OS_FINGERPRINT);
+ 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();
+
+ if (depth == outerDepth + 1) {
+ switch (tag) {
+ case TAG_LAUNCHER: {
+ ret.mLastKnownLauncher = ShortcutService.parseComponentNameAttribute(
+ parser, ATTR_VALUE);
+ continue;
+ }
+ case ShortcutPackage.TAG_ROOT: {
+ final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(
+ s, ret, parser, fromBackup);
+
+ // Don't use addShortcut(), we don't need to save the icon.
+ ret.mPackages.put(shortcuts.getPackageName(), shortcuts);
+ continue;
+ }
+
+ case ShortcutLauncher.TAG_ROOT: {
+ ret.addLauncher(
+ ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup));
+ continue;
+ }
}
}
+ ShortcutService.warnForInvalidTag(depth, tag);
}
- ShortcutService.warnForInvalidTag(depth, tag);
+ } catch (RuntimeException e) {
+ throw new ShortcutService.InvalidFileFormatException(
+ "Unable to parse file", e);
}
return ret;
}
}
}
+ public void mergeRestoredFile(ShortcutUser restored) {
+ final ShortcutService s = mService;
+ // Note, a restore happens only at the end of setup wizard. At this point, no apps are
+ // installed from Play Store yet, but it's still possible that system apps have already
+ // published dynamic shortcuts, since some apps do so on BOOT_COMPLETED.
+ // When such a system app has allowbackup=true, then we go ahead and replace all existing
+ // shortcuts with the restored shortcuts. (Then we'll re-publish manifest shortcuts later
+ // in the call site.)
+ // When such a system app has allowbackup=false, then we'll keep the shortcuts that have
+ // already been published. So we selectively add restored ShortcutPackages here.
+ //
+ // The same logic applies to launchers, but since launchers shouldn't pin shortcuts
+ // without users interaction it's really not a big deal, so we just clear existing
+ // ShortcutLauncher instances in mLaunchers and add all the restored ones here.
+
+ mLaunchers.clear();
+ restored.forAllLaunchers(sl -> {
+ // If the app is already installed and allowbackup = false, then ignore the restored
+ // data.
+ if (s.isPackageInstalled(sl.getPackageName(), getUserId())
+ && !s.shouldBackupApp(sl.getPackageName(), getUserId())) {
+ return;
+ }
+ addLauncher(sl);
+ });
+ restored.forAllPackages(sp -> {
+ // If the app is already installed and allowbackup = false, then ignore the restored
+ // data.
+ if (s.isPackageInstalled(sp.getPackageName(), getUserId())
+ && !s.shouldBackupApp(sp.getPackageName(), getUserId())) {
+ return;
+ }
+
+ final ShortcutPackage previous = getPackageShortcutsIfExists(sp.getPackageName());
+ if (previous != null && previous.hasNonManifestShortcuts()) {
+ Log.w(TAG, "Shortcuts for package " + sp.getPackageName() + " are being restored."
+ + " Existing non-manifeset shortcuts will be overwritten.");
+ }
+ addPackage(sp);
+ });
+ // Empty the launchers and packages in restored to avoid accidentally using them.
+ restored.mLaunchers.clear();
+ restored.mPackages.clear();
+ }
+
public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
pw.print(prefix);
pw.print("User: ");
/**
* It's the case with preintalled apps -- when applyRestore() is called, the system
* apps are already installed, so manifest shortcuts need to be re-published.
+ *
+ * Also, when a restore target app is already installed, and
+ * - if it has allowBackup=true, we'll restore normally, so all existing shortcuts will be
+ * replaced. (but manifest shortcuts will be re-published anyway.) We log a warning on
+ * logcat.
+ * - if it has allowBackup=false, we don't touch any of the existing shortcuts.
*/
public void testBackupAndRestore_appAlreadyInstalledWhenRestored() {
// Pre-backup. Same as testBackupAndRestore_manifestRePublished().
mService.mPackageMonitor.onReceive(mServiceContext,
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+ // Set up shortcuts for package 3, which won't be backed up / restored.
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_3, ShortcutActivity.class.getName()),
+ R.xml.shortcut_1);
+ updatePackageVersion(CALLING_PACKAGE_3, 1);
+ mService.mPackageMonitor.onReceive(mServiceContext,
+ genPackageAddIntent(CALLING_PACKAGE_3, USER_0));
+
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertTrue(getManager().setDynamicShortcuts(list(
+ makeShortcut("s1"))));
+ });
+
// Make sure the manifest shortcuts have been published.
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertWith(getCallerShortcuts())
.areAllDisabled();
});
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertWith(getCallerShortcuts())
+ .haveIds("s1", "ms1");
+ });
+
// Backup and *without restarting the service, just call applyRestore()*.
{
int prevUid = mInjectedCallingUid;
.areAllNotDynamic()
;
});
+
+ // Package 3 still has the same shortcuts.
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ assertWith(getCallerShortcuts())
+ .haveIds("s1", "ms1");
+ });
}
public void testSaveAndLoad_crossProfile() {