Merge "Shortcut: Improve backup & restore" into nyc-mr1-dev
This commit is contained in:
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,13 +334,16 @@ class ShortcutUser {
|
|||||||
throws IOException, XmlPullParserException {
|
throws IOException, XmlPullParserException {
|
||||||
out.startTag(null, TAG_ROOT);
|
out.startTag(null, TAG_ROOT);
|
||||||
|
|
||||||
ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales);
|
if (!forBackup) {
|
||||||
ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME,
|
// Don't have to back them up.
|
||||||
mLastAppScanTime);
|
ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales);
|
||||||
ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_OS_FINGERPRINT,
|
ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME,
|
||||||
mLastAppScanOsFingerprint);
|
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.
|
// Can't use forEachPackageItem due to the checked exceptions.
|
||||||
{
|
{
|
||||||
@@ -365,54 +376,59 @@ 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);
|
||||||
|
|
||||||
ret.mKnownLocales = ShortcutService.parseStringAttribute(parser,
|
try {
|
||||||
ATTR_KNOWN_LOCALES);
|
ret.mKnownLocales = ShortcutService.parseStringAttribute(parser,
|
||||||
|
ATTR_KNOWN_LOCALES);
|
||||||
|
|
||||||
// If lastAppScanTime is in the future, that means the clock went backwards.
|
// If lastAppScanTime is in the future, that means the clock went backwards.
|
||||||
// Just scan all apps again.
|
// Just scan all apps again.
|
||||||
final long lastAppScanTime = ShortcutService.parseLongAttribute(parser,
|
final long lastAppScanTime = ShortcutService.parseLongAttribute(parser,
|
||||||
ATTR_LAST_APP_SCAN_TIME);
|
ATTR_LAST_APP_SCAN_TIME);
|
||||||
final long currentTime = s.injectCurrentTimeMillis();
|
final long currentTime = s.injectCurrentTimeMillis();
|
||||||
ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0;
|
ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0;
|
||||||
ret.mLastAppScanOsFingerprint = ShortcutService.parseStringAttribute(parser,
|
ret.mLastAppScanOsFingerprint = ShortcutService.parseStringAttribute(parser,
|
||||||
ATTR_LAST_APP_SCAN_OS_FINGERPRINT);
|
ATTR_LAST_APP_SCAN_OS_FINGERPRINT);
|
||||||
final int outerDepth = parser.getDepth();
|
final int outerDepth = parser.getDepth();
|
||||||
int type;
|
int type;
|
||||||
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
|
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
|
||||||
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
|
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
|
||||||
if (type != XmlPullParser.START_TAG) {
|
if (type != XmlPullParser.START_TAG) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final int depth = parser.getDepth();
|
final int depth = parser.getDepth();
|
||||||
final String tag = parser.getName();
|
final String tag = parser.getName();
|
||||||
|
|
||||||
if (depth == outerDepth + 1) {
|
if (depth == outerDepth + 1) {
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
case TAG_LAUNCHER: {
|
case TAG_LAUNCHER: {
|
||||||
ret.mLastKnownLauncher = ShortcutService.parseComponentNameAttribute(
|
ret.mLastKnownLauncher = ShortcutService.parseComponentNameAttribute(
|
||||||
parser, ATTR_VALUE);
|
parser, ATTR_VALUE);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
case ShortcutPackage.TAG_ROOT: {
|
case ShortcutPackage.TAG_ROOT: {
|
||||||
final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(
|
final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(
|
||||||
s, ret, parser, fromBackup);
|
s, ret, parser, fromBackup);
|
||||||
|
|
||||||
// Don't use addShortcut(), we don't need to save the icon.
|
// Don't use addShortcut(), we don't need to save the icon.
|
||||||
ret.mPackages.put(shortcuts.getPackageName(), shortcuts);
|
ret.mPackages.put(shortcuts.getPackageName(), shortcuts);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
case ShortcutLauncher.TAG_ROOT: {
|
case ShortcutLauncher.TAG_ROOT: {
|
||||||
ret.addLauncher(
|
ret.addLauncher(
|
||||||
ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup));
|
ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup));
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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: ");
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
Reference in New Issue
Block a user