Merge "Shortcut: Improve backup & restore" into nyc-mr1-dev

This commit is contained in:
Makoto Onuki
2016-08-30 19:57:34 +00:00
committed by Android (Google) Code Review
5 changed files with 170 additions and 53 deletions

View File

@@ -1151,6 +1151,17 @@ class ShortcutPackage extends ShortcutPackageItem {
} }
} }
/** @return true if there's any shortcuts that are not manifest shortcuts. */
public boolean hasNonManifestShortcuts() {
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
if (!si.isDeclaredInManifest()) {
return true;
}
}
return false;
}
public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
pw.println(); pw.println();

View File

@@ -40,7 +40,7 @@ abstract class ShortcutPackageItem {
private final ShortcutPackageInfo mPackageInfo; private final ShortcutPackageInfo mPackageInfo;
protected final ShortcutUser mShortcutUser; protected ShortcutUser mShortcutUser;
protected ShortcutPackageItem(@NonNull ShortcutUser shortcutUser, protected ShortcutPackageItem(@NonNull ShortcutUser shortcutUser,
int packageUserId, @NonNull String packageName, int packageUserId, @NonNull String packageName,
@@ -51,6 +51,13 @@ abstract class ShortcutPackageItem {
mPackageInfo = Preconditions.checkNotNull(packageInfo); mPackageInfo = Preconditions.checkNotNull(packageInfo);
} }
/**
* Change the parent {@link ShortcutUser}. Need it in the restore code.
*/
public void replaceUser(ShortcutUser user) {
mShortcutUser = user;
}
public ShortcutUser getUser() { public ShortcutUser getUser() {
return mShortcutUser; return mShortcutUser;
} }

View File

@@ -380,6 +380,12 @@ public class ShortcutService extends IShortcutService.Stub {
@GuardedBy("mLock") @GuardedBy("mLock")
private Exception mLastWtfStacktrace; private Exception mLastWtfStacktrace;
static class InvalidFileFormatException extends Exception {
public InvalidFileFormatException(String message, Throwable cause) {
super(message, cause);
}
}
public ShortcutService(Context context) { public ShortcutService(Context context) {
this(context, BackgroundThread.get().getLooper(), /*onyForPackgeManagerApis*/ false); this(context, BackgroundThread.get().getLooper(), /*onyForPackgeManagerApis*/ false);
} }
@@ -961,7 +967,7 @@ public class ShortcutService extends IShortcutService.Stub {
try { try {
final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false); final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false);
return ret; return ret;
} catch (IOException | XmlPullParserException e) { } catch (IOException | XmlPullParserException | InvalidFileFormatException e) {
Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e); Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
return null; return null;
} finally { } finally {
@@ -970,7 +976,8 @@ public class ShortcutService extends IShortcutService.Stub {
} }
private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is, 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); final BufferedInputStream bis = new BufferedInputStream(is);
@@ -3170,15 +3177,16 @@ public class ShortcutService extends IShortcutService.Stub {
wtf("Can't restore: user " + userId + " is locked or not running"); wtf("Can't restore: user " + userId + " is locked or not running");
return; return;
} }
final ShortcutUser user; // Actually do restore.
final ShortcutUser restored;
final ByteArrayInputStream is = new ByteArrayInputStream(payload); final ByteArrayInputStream is = new ByteArrayInputStream(payload);
try { try {
user = loadUserInternal(userId, is, /* fromBackup */ true); restored = loadUserInternal(userId, is, /* fromBackup */ true);
} catch (XmlPullParserException | IOException e) { } catch (XmlPullParserException | IOException | InvalidFileFormatException e) {
Slog.w(TAG, "Restoration failed.", e); Slog.w(TAG, "Restoration failed.", e);
return; return;
} }
mUsers.put(userId, user); getUserShortcutsLocked(userId).mergeRestoredFile(restored);
// Rescan all packages to re-publish manifest shortcuts and do other checks. // Rescan all packages to re-publish manifest shortcuts and do other checks.
rescanUpdatedPackagesLocked(userId, rescanUpdatedPackagesLocked(userId,

View File

@@ -23,12 +23,14 @@ import android.content.pm.ShortcutManager;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.format.Formatter; import android.text.format.Formatter;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog; import android.util.Slog;
import android.util.SparseArray; import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions; import com.android.internal.util.Preconditions;
import com.android.server.pm.ShortcutService.InvalidFileFormatException;
import libcore.util.Objects; import libcore.util.Objects;
@@ -164,6 +166,11 @@ class ShortcutUser {
return mPackages.containsKey(packageName); return mPackages.containsKey(packageName);
} }
private void addPackage(@NonNull ShortcutPackage p) {
p.replaceUser(this);
mPackages.put(p.getPackageName(), p);
}
public ShortcutPackage removePackage(@NonNull String packageName) { public ShortcutPackage removePackage(@NonNull String packageName) {
final ShortcutPackage removed = mPackages.remove(packageName); final ShortcutPackage removed = mPackages.remove(packageName);
@@ -179,7 +186,8 @@ class ShortcutUser {
return mLaunchers; return mLaunchers;
} }
public void addLauncher(ShortcutLauncher launcher) { private void addLauncher(ShortcutLauncher launcher) {
launcher.replaceUser(this);
mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(), mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(),
launcher.getPackageName()), launcher); launcher.getPackageName()), launcher);
} }
@@ -326,6 +334,8 @@ class ShortcutUser {
throws IOException, XmlPullParserException { throws IOException, XmlPullParserException {
out.startTag(null, TAG_ROOT); out.startTag(null, TAG_ROOT);
if (!forBackup) {
// Don't have to back them up.
ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales); ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales);
ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME, ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME,
mLastAppScanTime); mLastAppScanTime);
@@ -333,6 +343,7 @@ class ShortcutUser {
mLastAppScanOsFingerprint); mLastAppScanOsFingerprint);
ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLastKnownLauncher); ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLastKnownLauncher);
}
// Can't use forEachPackageItem due to the checked exceptions. // Can't use forEachPackageItem due to the checked exceptions.
{ {
@@ -365,9 +376,10 @@ class ShortcutUser {
} }
public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId, 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); final ShortcutUser ret = new ShortcutUser(s, userId);
try {
ret.mKnownLocales = ShortcutService.parseStringAttribute(parser, ret.mKnownLocales = ShortcutService.parseStringAttribute(parser,
ATTR_KNOWN_LOCALES); ATTR_KNOWN_LOCALES);
@@ -414,6 +426,10 @@ class ShortcutUser {
} }
ShortcutService.warnForInvalidTag(depth, tag); ShortcutService.warnForInvalidTag(depth, tag);
} }
} catch (RuntimeException e) {
throw new ShortcutService.InvalidFileFormatException(
"Unable to parse file", e);
}
return ret; return ret;
} }
@@ -461,6 +477,51 @@ class ShortcutUser {
} }
} }
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) { public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
pw.print(prefix); pw.print(prefix);
pw.print("User: "); pw.print("User: ");

View File

@@ -5359,6 +5359,12 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
/** /**
* It's the case with preintalled apps -- when applyRestore() is called, the system * 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. * 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() { public void testBackupAndRestore_appAlreadyInstalledWhenRestored() {
// Pre-backup. Same as testBackupAndRestore_manifestRePublished(). // Pre-backup. Same as testBackupAndRestore_manifestRePublished().
@@ -5390,6 +5396,19 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
mService.mPackageMonitor.onReceive(mServiceContext, mService.mPackageMonitor.onReceive(mServiceContext,
genPackageAddIntent(CALLING_PACKAGE_1, USER_0)); 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. // Make sure the manifest shortcuts have been published.
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertWith(getCallerShortcuts()) assertWith(getCallerShortcuts())
@@ -5415,6 +5434,11 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
.areAllDisabled(); .areAllDisabled();
}); });
runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
assertWith(getCallerShortcuts())
.haveIds("s1", "ms1");
});
// Backup and *without restarting the service, just call applyRestore()*. // Backup and *without restarting the service, just call applyRestore()*.
{ {
int prevUid = mInjectedCallingUid; int prevUid = mInjectedCallingUid;
@@ -5454,6 +5478,12 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
.areAllNotDynamic() .areAllNotDynamic()
; ;
}); });
// Package 3 still has the same shortcuts.
runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
assertWith(getCallerShortcuts())
.haveIds("s1", "ms1");
});
} }
public void testSaveAndLoad_crossProfile() { public void testSaveAndLoad_crossProfile() {