Merge changes from topic "migrate_CtsStagedInstallTest_RollbackTest"
* changes: Add multi-user test for the general non-staged case Increase timeout in testNativeWatchdogTriggersRollback Fix flaky testEnableRollbackTimeoutFailsRollback test Wait for available rollbacks in RollbackTest. Include NewRollbacks in result of getAvailableRollbacks. Rename RollbackData to Rollback. Remove INSTALLED_USERS extra from ACTION_PACKAGE_ENABLE_ROLLBACK. Update to new RollbackUtils.sendCrashBroadcast API. Extend ROLLBACK_COMMITTED broadcast for the multi-user case Fix NullPointerException. Cleanup some obsolete TODOs and unused code. Test Native Watchdog triggers RollbackManager rollback Uninstall testapps before running SecondaryUserRollbackTest delete ENABLING rollback if we can't get session info for it. Add test for rollback of userdata with multiple rollbacks Load rollback data in RollbackManagerServiceImpl constructor. Use "commit" for rollbacks instead of "execute". Add TestApi to block the RollbackManagerHandlerThread. Use Slog in RollbackManager, not Log Make setRequestDowngrade @TestApi Migrate RollbackTest and StagedRollbackTest to cts utils Add .apex suffix to apex files during validation Add host side test for secondary user rollback Reduce ENABLE_ROLLBACK timeout to 0 for timeout test.
This commit is contained in:
@@ -694,6 +694,7 @@ package android.content.pm {
|
||||
method public void setEnableRollback(boolean);
|
||||
method @RequiresPermission("android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS") public void setGrantedRuntimePermissions(String[]);
|
||||
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setInstallAsApex();
|
||||
method public void setRequestDowngrade(boolean);
|
||||
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setStaged();
|
||||
}
|
||||
|
||||
@@ -795,6 +796,7 @@ package android.content.rollback {
|
||||
}
|
||||
|
||||
public final class RollbackManager {
|
||||
method @RequiresPermission(android.Manifest.permission.TEST_MANAGE_ROLLBACKS) public void blockRollbackManager(long);
|
||||
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_ROLLBACKS, android.Manifest.permission.TEST_MANAGE_ROLLBACKS}) public void commitRollback(int, @NonNull java.util.List<android.content.pm.VersionedPackage>, @NonNull android.content.IntentSender);
|
||||
method @RequiresPermission(android.Manifest.permission.TEST_MANAGE_ROLLBACKS) public void expireRollbackForPackage(@NonNull String);
|
||||
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_ROLLBACKS, android.Manifest.permission.TEST_MANAGE_ROLLBACKS}) public java.util.List<android.content.rollback.RollbackInfo> getAvailableRollbacks();
|
||||
|
||||
@@ -1565,7 +1565,7 @@ public class PackageInstaller {
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
@SystemApi
|
||||
@SystemApi @TestApi
|
||||
public void setRequestDowngrade(boolean requestDowngrade) {
|
||||
if (requestDowngrade) {
|
||||
installFlags |= PackageManager.INSTALL_REQUEST_DOWNGRADE;
|
||||
|
||||
@@ -889,12 +889,6 @@ public abstract class PackageManagerInternal {
|
||||
public static final String EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS =
|
||||
"android.content.pm.extra.ENABLE_ROLLBACK_INSTALL_FLAGS";
|
||||
|
||||
/**
|
||||
* Extra field name for the set of installed users for a given rollback package.
|
||||
*/
|
||||
public static final String EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS =
|
||||
"android.content.pm.extra.ENABLE_ROLLBACK_INSTALLED_USERS";
|
||||
|
||||
/**
|
||||
* Extra field name for the user id an install is associated with when
|
||||
* enabling rollback.
|
||||
|
||||
@@ -254,6 +254,8 @@ public class PackageParser {
|
||||
|
||||
/** @hide */
|
||||
public static final String APK_FILE_EXTENSION = ".apk";
|
||||
/** @hide */
|
||||
public static final String APEX_FILE_EXTENSION = ".apex";
|
||||
|
||||
/** @hide */
|
||||
public static class NewPermissionInfo {
|
||||
|
||||
@@ -24,7 +24,7 @@ import android.content.IntentSender;
|
||||
interface IRollbackManager {
|
||||
|
||||
ParceledListSlice getAvailableRollbacks();
|
||||
ParceledListSlice getRecentlyExecutedRollbacks();
|
||||
ParceledListSlice getRecentlyCommittedRollbacks();
|
||||
|
||||
void commitRollback(int rollbackId, in ParceledListSlice causePackages,
|
||||
String callerPackageName, in IntentSender statusReceiver);
|
||||
@@ -51,4 +51,7 @@ interface IRollbackManager {
|
||||
// Used by the staging manager to notify the RollbackManager of the apk
|
||||
// session for a staged session.
|
||||
void notifyStagedApkSession(int originalSessionId, int apkSessionId);
|
||||
|
||||
// For test purposes only.
|
||||
void blockRollbackManager(long millis);
|
||||
}
|
||||
|
||||
@@ -76,10 +76,10 @@ public final class PackageRollbackInfo implements Parcelable {
|
||||
private final boolean mIsApex;
|
||||
|
||||
/*
|
||||
* The list of users the package is installed for.
|
||||
* The list of users for which snapshots have been saved.
|
||||
*/
|
||||
// NOTE: Not a part of the Parcelable representation of this object.
|
||||
private final IntArray mInstalledUsers;
|
||||
private final IntArray mSnapshottedUsers;
|
||||
|
||||
/**
|
||||
* A mapping between user and an inode of theirs CE data snapshot.
|
||||
@@ -148,8 +148,8 @@ public final class PackageRollbackInfo implements Parcelable {
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public IntArray getInstalledUsers() {
|
||||
return mInstalledUsers;
|
||||
public IntArray getSnapshottedUsers() {
|
||||
return mSnapshottedUsers;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@@ -179,14 +179,14 @@ public final class PackageRollbackInfo implements Parcelable {
|
||||
public PackageRollbackInfo(VersionedPackage packageRolledBackFrom,
|
||||
VersionedPackage packageRolledBackTo,
|
||||
@NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores,
|
||||
boolean isApex, @NonNull IntArray installedUsers,
|
||||
boolean isApex, @NonNull IntArray snapshottedUsers,
|
||||
@NonNull SparseLongArray ceSnapshotInodes) {
|
||||
this.mVersionRolledBackFrom = packageRolledBackFrom;
|
||||
this.mVersionRolledBackTo = packageRolledBackTo;
|
||||
this.mPendingBackups = pendingBackups;
|
||||
this.mPendingRestores = pendingRestores;
|
||||
this.mIsApex = isApex;
|
||||
this.mInstalledUsers = installedUsers;
|
||||
this.mSnapshottedUsers = snapshottedUsers;
|
||||
this.mCeSnapshotInodes = ceSnapshotInodes;
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ public final class PackageRollbackInfo implements Parcelable {
|
||||
this.mIsApex = in.readBoolean();
|
||||
this.mPendingRestores = null;
|
||||
this.mPendingBackups = null;
|
||||
this.mInstalledUsers = null;
|
||||
this.mSnapshottedUsers = null;
|
||||
this.mCeSnapshotInodes = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ public final class RollbackManager {
|
||||
})
|
||||
public @NonNull List<RollbackInfo> getRecentlyCommittedRollbacks() {
|
||||
try {
|
||||
return mBinder.getRecentlyExecutedRollbacks().getList();
|
||||
return mBinder.getRecentlyCommittedRollbacks().getList();
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
@@ -250,4 +250,25 @@ public final class RollbackManager {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Block the RollbackManager for a specified amount of time.
|
||||
* This API is meant to facilitate testing of race conditions in
|
||||
* RollbackManager. Blocks RollbackManager from processing anything for
|
||||
* the given number of milliseconds.
|
||||
*
|
||||
* @param millis number of milliseconds to block the RollbackManager for
|
||||
* @throws SecurityException if the caller does not have appropriate permissions.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.TEST_MANAGE_ROLLBACKS)
|
||||
@TestApi
|
||||
public void blockRollbackManager(long millis) {
|
||||
try {
|
||||
mBinder.blockRollbackManager(millis);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STOR
|
||||
import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
|
||||
import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
|
||||
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT;
|
||||
import static android.content.pm.PackageParser.APEX_FILE_EXTENSION;
|
||||
import static android.content.pm.PackageParser.APK_FILE_EXTENSION;
|
||||
import static android.system.OsConstants.O_CREAT;
|
||||
import static android.system.OsConstants.O_RDONLY;
|
||||
@@ -1484,7 +1485,29 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
"Too many files for apex install");
|
||||
}
|
||||
|
||||
mResolvedBaseFile = addedFiles[0];
|
||||
try {
|
||||
resolveStageDirLocked();
|
||||
} catch (IOException e) {
|
||||
throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
|
||||
"Failed to resolve stage location", e);
|
||||
}
|
||||
|
||||
File addedFile = addedFiles[0]; // there is only one file
|
||||
|
||||
// Ensure file name has proper suffix
|
||||
final String sourceName = addedFile.getName();
|
||||
final String targetName = sourceName.endsWith(APEX_FILE_EXTENSION)
|
||||
? sourceName
|
||||
: sourceName + APEX_FILE_EXTENSION;
|
||||
if (!FileUtils.isValidExtFilename(targetName)) {
|
||||
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
|
||||
"Invalid filename: " + targetName);
|
||||
}
|
||||
|
||||
final File targetFile = new File(mResolvedStageDir, targetName);
|
||||
resolveAndStageFile(addedFile, targetFile);
|
||||
|
||||
mResolvedBaseFile = targetFile;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15092,17 +15092,6 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
TRACE_TAG_PACKAGE_MANAGER, "enable_rollback", enableRollbackToken);
|
||||
mPendingEnableRollback.append(enableRollbackToken, this);
|
||||
|
||||
final int[] installedUsers;
|
||||
synchronized (mPackages) {
|
||||
PackageSetting ps = mSettings.getPackageLPr(pkgLite.packageName);
|
||||
if (ps != null) {
|
||||
installedUsers = ps.queryInstalledUsers(sUserManager.getUserIds(),
|
||||
true);
|
||||
} else {
|
||||
installedUsers = new int[0];
|
||||
}
|
||||
}
|
||||
|
||||
Intent enableRollbackIntent = new Intent(Intent.ACTION_PACKAGE_ENABLE_ROLLBACK);
|
||||
enableRollbackIntent.putExtra(
|
||||
PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_TOKEN,
|
||||
@@ -15110,9 +15099,6 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
enableRollbackIntent.putExtra(
|
||||
PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS,
|
||||
installFlags);
|
||||
enableRollbackIntent.putExtra(
|
||||
PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS,
|
||||
installedUsers);
|
||||
enableRollbackIntent.putExtra(
|
||||
PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_USER,
|
||||
getRollbackUser().getIdentifier());
|
||||
|
||||
@@ -20,7 +20,7 @@ import android.content.rollback.PackageRollbackInfo;
|
||||
import android.content.rollback.PackageRollbackInfo.RestoreInfo;
|
||||
import android.os.storage.StorageManager;
|
||||
import android.util.IntArray;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseLongArray;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
@@ -52,17 +52,18 @@ public class AppDataRollbackHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an app data snapshot for a specified {@code packageRollbackInfo}. Updates said {@code
|
||||
* packageRollbackInfo} with the inodes of the CE user data snapshot folders.
|
||||
* Creates an app data snapshot for a specified {@code packageRollbackInfo} and the specified
|
||||
* {@code userIds}. Updates said {@code packageRollbackInfo} with the inodes of the CE user data
|
||||
* snapshot folders.
|
||||
*/
|
||||
public void snapshotAppData(int snapshotId, PackageRollbackInfo packageRollbackInfo) {
|
||||
final int[] installedUsers = packageRollbackInfo.getInstalledUsers().toArray();
|
||||
for (int user : installedUsers) {
|
||||
public void snapshotAppData(
|
||||
int snapshotId, PackageRollbackInfo packageRollbackInfo, int[] userIds) {
|
||||
for (int user : userIds) {
|
||||
final int storageFlags;
|
||||
if (isUserCredentialLocked(user)) {
|
||||
// We've encountered a user that hasn't unlocked on a FBE device, so we can't copy
|
||||
// across app user data until the user unlocks their device.
|
||||
Log.v(TAG, "User: " + user + " isn't unlocked, skipping CE userdata backup.");
|
||||
Slog.v(TAG, "User: " + user + " isn't unlocked, skipping CE userdata backup.");
|
||||
storageFlags = Installer.FLAG_STORAGE_DE;
|
||||
packageRollbackInfo.addPendingBackup(user);
|
||||
} else {
|
||||
@@ -76,10 +77,11 @@ public class AppDataRollbackHelper {
|
||||
packageRollbackInfo.putCeSnapshotInode(user, ceSnapshotInode);
|
||||
}
|
||||
} catch (InstallerException ie) {
|
||||
Log.e(TAG, "Unable to create app data snapshot for: "
|
||||
Slog.e(TAG, "Unable to create app data snapshot for: "
|
||||
+ packageRollbackInfo.getPackageName() + ", userId: " + user, ie);
|
||||
}
|
||||
}
|
||||
packageRollbackInfo.getSnapshottedUsers().addAll(IntArray.wrap(userIds));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,14 +98,14 @@ public class AppDataRollbackHelper {
|
||||
|
||||
final IntArray pendingBackups = packageRollbackInfo.getPendingBackups();
|
||||
final List<RestoreInfo> pendingRestores = packageRollbackInfo.getPendingRestores();
|
||||
boolean changedRollbackData = false;
|
||||
boolean changedRollback = false;
|
||||
|
||||
// If we still have a userdata backup pending for this user, it implies that the user
|
||||
// hasn't unlocked their device between the point of backup and the point of restore,
|
||||
// so the data cannot have changed. We simply skip restoring CE data in this case.
|
||||
if (pendingBackups != null && pendingBackups.indexOf(userId) != -1) {
|
||||
pendingBackups.remove(pendingBackups.indexOf(userId));
|
||||
changedRollbackData = true;
|
||||
changedRollback = true;
|
||||
} else {
|
||||
// There's no pending CE backup for this user, which means that we successfully
|
||||
// managed to backup data for the user, which means we seek to restore it
|
||||
@@ -111,7 +113,7 @@ public class AppDataRollbackHelper {
|
||||
// We've encountered a user that hasn't unlocked on a FBE device, so we can't
|
||||
// copy across app user data until the user unlocks their device.
|
||||
pendingRestores.add(new RestoreInfo(userId, appId, seInfo));
|
||||
changedRollbackData = true;
|
||||
changedRollback = true;
|
||||
} else {
|
||||
// This user has unlocked, we can proceed to restore both CE and DE data.
|
||||
storageFlags = storageFlags | Installer.FLAG_STORAGE_CE;
|
||||
@@ -122,11 +124,11 @@ public class AppDataRollbackHelper {
|
||||
mInstaller.restoreAppDataSnapshot(packageRollbackInfo.getPackageName(), appId, seInfo,
|
||||
userId, rollbackId, storageFlags);
|
||||
} catch (InstallerException ie) {
|
||||
Log.e(TAG, "Unable to restore app data snapshot: "
|
||||
Slog.e(TAG, "Unable to restore app data snapshot: "
|
||||
+ packageRollbackInfo.getPackageName(), ie);
|
||||
}
|
||||
|
||||
return changedRollbackData;
|
||||
return changedRollback;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,7 +150,7 @@ public class AppDataRollbackHelper {
|
||||
ceSnapshotInodes.delete(user);
|
||||
}
|
||||
} catch (InstallerException ie) {
|
||||
Log.e(TAG, "Unable to delete app data snapshot for "
|
||||
Slog.e(TAG, "Unable to delete app data snapshot for "
|
||||
+ packageRollbackInfo.getPackageName(), ie);
|
||||
}
|
||||
}
|
||||
@@ -158,29 +160,29 @@ public class AppDataRollbackHelper {
|
||||
* Packages pending backup for the given user are added to {@code pendingBackupPackages} along
|
||||
* with their corresponding {@code PackageRollbackInfo}.
|
||||
*
|
||||
* @return the list of {@code RollbackData} that has pending backups. Note that some of the
|
||||
* @return the list of rollbacks that have pending backups. Note that some of the
|
||||
* backups won't be performed, because they might be counteracted by pending restores.
|
||||
*/
|
||||
private static List<RollbackData> computePendingBackups(int userId,
|
||||
private static List<Rollback> computePendingBackups(int userId,
|
||||
Map<String, PackageRollbackInfo> pendingBackupPackages,
|
||||
List<RollbackData> rollbacks) {
|
||||
List<RollbackData> rd = new ArrayList<>();
|
||||
List<Rollback> rollbacks) {
|
||||
List<Rollback> rollbacksWithPendingBackups = new ArrayList<>();
|
||||
|
||||
for (RollbackData data : rollbacks) {
|
||||
for (PackageRollbackInfo info : data.info.getPackages()) {
|
||||
for (Rollback rollback : rollbacks) {
|
||||
for (PackageRollbackInfo info : rollback.info.getPackages()) {
|
||||
final IntArray pendingBackupUsers = info.getPendingBackups();
|
||||
if (pendingBackupUsers != null) {
|
||||
final int idx = pendingBackupUsers.indexOf(userId);
|
||||
if (idx != -1) {
|
||||
pendingBackupPackages.put(info.getPackageName(), info);
|
||||
if (rd.indexOf(data) == -1) {
|
||||
rd.add(data);
|
||||
if (rollbacksWithPendingBackups.indexOf(rollback) == -1) {
|
||||
rollbacksWithPendingBackups.add(rollback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rd;
|
||||
return rollbacksWithPendingBackups;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,45 +190,45 @@ public class AppDataRollbackHelper {
|
||||
* Packages pending restore are added to {@code pendingRestores} along with their corresponding
|
||||
* {@code PackageRollbackInfo}.
|
||||
*
|
||||
* @return the list of {@code RollbackData} that has pending restores. Note that some of the
|
||||
* @return the list of rollbacks that have pending restores. Note that some of the
|
||||
* restores won't be performed, because they might be counteracted by pending backups.
|
||||
*/
|
||||
private static List<RollbackData> computePendingRestores(int userId,
|
||||
private static List<Rollback> computePendingRestores(int userId,
|
||||
Map<String, PackageRollbackInfo> pendingRestorePackages,
|
||||
List<RollbackData> rollbacks) {
|
||||
List<RollbackData> rd = new ArrayList<>();
|
||||
List<Rollback> rollbacks) {
|
||||
List<Rollback> rollbacksWithPendingRestores = new ArrayList<>();
|
||||
|
||||
for (RollbackData data : rollbacks) {
|
||||
for (PackageRollbackInfo info : data.info.getPackages()) {
|
||||
for (Rollback rollback : rollbacks) {
|
||||
for (PackageRollbackInfo info : rollback.info.getPackages()) {
|
||||
final RestoreInfo ri = info.getRestoreInfo(userId);
|
||||
if (ri != null) {
|
||||
pendingRestorePackages.put(info.getPackageName(), info);
|
||||
if (rd.indexOf(data) == -1) {
|
||||
rd.add(data);
|
||||
if (rollbacksWithPendingRestores.indexOf(rollback) == -1) {
|
||||
rollbacksWithPendingRestores.add(rollback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rd;
|
||||
return rollbacksWithPendingRestores;
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits the list of pending backups and restores for a given {@code userId}. For the pending
|
||||
* backups updates corresponding {@code changedRollbackData} with a mapping from {@code userId}
|
||||
* to a inode of theirs CE user data snapshot.
|
||||
* Commits the list of pending backups and restores for a given {@code userId}. For rollbacks
|
||||
* with pending backups, updates the {@code Rollback} instance with a mapping from
|
||||
* {@code userId} to inode of the CE user data snapshot.
|
||||
*
|
||||
* @return the set of {@code RollbackData} that have been changed and should be stored on disk.
|
||||
* @return the set of rollbacks with changes that should be stored on disk.
|
||||
*/
|
||||
public Set<RollbackData> commitPendingBackupAndRestoreForUser(int userId,
|
||||
List<RollbackData> rollbacks) {
|
||||
public Set<Rollback> commitPendingBackupAndRestoreForUser(int userId,
|
||||
List<Rollback> rollbacks) {
|
||||
|
||||
final Map<String, PackageRollbackInfo> pendingBackupPackages = new HashMap<>();
|
||||
final List<RollbackData> pendingBackups = computePendingBackups(userId,
|
||||
final List<Rollback> pendingBackups = computePendingBackups(userId,
|
||||
pendingBackupPackages, rollbacks);
|
||||
|
||||
final Map<String, PackageRollbackInfo> pendingRestorePackages = new HashMap<>();
|
||||
final List<RollbackData> pendingRestores = computePendingRestores(userId,
|
||||
final List<Rollback> pendingRestores = computePendingRestores(userId,
|
||||
pendingRestorePackages, rollbacks);
|
||||
|
||||
// First remove unnecessary backups, i.e. when user did not unlock their phone between the
|
||||
@@ -246,18 +248,19 @@ public class AppDataRollbackHelper {
|
||||
}
|
||||
|
||||
if (!pendingBackupPackages.isEmpty()) {
|
||||
for (RollbackData data : pendingBackups) {
|
||||
for (PackageRollbackInfo info : data.info.getPackages()) {
|
||||
for (Rollback rollback : pendingBackups) {
|
||||
for (PackageRollbackInfo info : rollback.info.getPackages()) {
|
||||
final IntArray pendingBackupUsers = info.getPendingBackups();
|
||||
final int idx = pendingBackupUsers.indexOf(userId);
|
||||
if (idx != -1) {
|
||||
try {
|
||||
long ceSnapshotInode = mInstaller.snapshotAppData(info.getPackageName(),
|
||||
userId, data.info.getRollbackId(), Installer.FLAG_STORAGE_CE);
|
||||
userId, rollback.info.getRollbackId(),
|
||||
Installer.FLAG_STORAGE_CE);
|
||||
info.putCeSnapshotInode(userId, ceSnapshotInode);
|
||||
pendingBackupUsers.remove(idx);
|
||||
} catch (InstallerException ie) {
|
||||
Log.e(TAG,
|
||||
Slog.e(TAG,
|
||||
"Unable to create app data snapshot for: "
|
||||
+ info.getPackageName() + ", userId: " + userId, ie);
|
||||
}
|
||||
@@ -267,17 +270,17 @@ public class AppDataRollbackHelper {
|
||||
}
|
||||
|
||||
if (!pendingRestorePackages.isEmpty()) {
|
||||
for (RollbackData data : pendingRestores) {
|
||||
for (PackageRollbackInfo info : data.info.getPackages()) {
|
||||
for (Rollback rollback : pendingRestores) {
|
||||
for (PackageRollbackInfo info : rollback.info.getPackages()) {
|
||||
final RestoreInfo ri = info.getRestoreInfo(userId);
|
||||
if (ri != null) {
|
||||
try {
|
||||
mInstaller.restoreAppDataSnapshot(info.getPackageName(), ri.appId,
|
||||
ri.seInfo, userId, data.info.getRollbackId(),
|
||||
ri.seInfo, userId, rollback.info.getRollbackId(),
|
||||
Installer.FLAG_STORAGE_CE);
|
||||
info.removeRestoreInfo(ri);
|
||||
} catch (InstallerException ie) {
|
||||
Log.e(TAG, "Unable to restore app data snapshot for: "
|
||||
Slog.e(TAG, "Unable to restore app data snapshot for: "
|
||||
+ info.getPackageName(), ie);
|
||||
}
|
||||
}
|
||||
@@ -285,7 +288,7 @@ public class AppDataRollbackHelper {
|
||||
}
|
||||
}
|
||||
|
||||
final Set<RollbackData> changed = new HashSet<>(pendingBackups);
|
||||
final Set<Rollback> changed = new HashSet<>(pendingBackups);
|
||||
changed.addAll(pendingRestores);
|
||||
return changed;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import java.util.ArrayList;
|
||||
* Information about a rollback available for a set of atomically installed
|
||||
* packages.
|
||||
*/
|
||||
class RollbackData {
|
||||
class Rollback {
|
||||
@IntDef(flag = true, prefix = { "ROLLBACK_STATE_" }, value = {
|
||||
ROLLBACK_STATE_ENABLING,
|
||||
ROLLBACK_STATE_AVAILABLE,
|
||||
@@ -102,13 +102,13 @@ class RollbackData {
|
||||
public boolean restoreUserDataInProgress = false;
|
||||
|
||||
/**
|
||||
* Constructs a new, empty RollbackData instance.
|
||||
* Constructs a new, empty Rollback instance.
|
||||
*
|
||||
* @param rollbackId the id of the rollback.
|
||||
* @param backupDir the directory where the rollback data is stored.
|
||||
* @param stagedSessionId the session id if this is a staged rollback, -1 otherwise.
|
||||
*/
|
||||
RollbackData(int rollbackId, File backupDir, int stagedSessionId) {
|
||||
Rollback(int rollbackId, File backupDir, int stagedSessionId) {
|
||||
this.info = new RollbackInfo(rollbackId,
|
||||
/* packages */ new ArrayList<>(),
|
||||
/* isStaged */ stagedSessionId != -1,
|
||||
@@ -121,9 +121,9 @@ class RollbackData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a RollbackData instance with full rollback data information.
|
||||
* Constructs a pre-populated Rollback instance.
|
||||
*/
|
||||
RollbackData(RollbackInfo info, File backupDir, Instant timestamp, int stagedSessionId,
|
||||
Rollback(RollbackInfo info, File backupDir, Instant timestamp, int stagedSessionId,
|
||||
@RollbackState int state, int apkSessionId, boolean restoreUserDataInProgress) {
|
||||
this.info = info;
|
||||
this.backupDir = backupDir;
|
||||
@@ -143,9 +143,9 @@ class RollbackData {
|
||||
|
||||
static String rollbackStateToString(@RollbackState int state) {
|
||||
switch (state) {
|
||||
case RollbackData.ROLLBACK_STATE_ENABLING: return "enabling";
|
||||
case RollbackData.ROLLBACK_STATE_AVAILABLE: return "available";
|
||||
case RollbackData.ROLLBACK_STATE_COMMITTED: return "committed";
|
||||
case Rollback.ROLLBACK_STATE_ENABLING: return "enabling";
|
||||
case Rollback.ROLLBACK_STATE_AVAILABLE: return "available";
|
||||
case Rollback.ROLLBACK_STATE_COMMITTED: return "committed";
|
||||
}
|
||||
throw new AssertionError("Invalid rollback state: " + state);
|
||||
}
|
||||
@@ -153,9 +153,9 @@ class RollbackData {
|
||||
static @RollbackState int rollbackStateFromString(String state)
|
||||
throws ParseException {
|
||||
switch (state) {
|
||||
case "enabling": return RollbackData.ROLLBACK_STATE_ENABLING;
|
||||
case "available": return RollbackData.ROLLBACK_STATE_AVAILABLE;
|
||||
case "committed": return RollbackData.ROLLBACK_STATE_COMMITTED;
|
||||
case "enabling": return Rollback.ROLLBACK_STATE_ENABLING;
|
||||
case "available": return Rollback.ROLLBACK_STATE_AVAILABLE;
|
||||
case "committed": return Rollback.ROLLBACK_STATE_COMMITTED;
|
||||
}
|
||||
throw new ParseException("Invalid rollback state: " + state, 0);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,8 +16,8 @@
|
||||
|
||||
package com.android.server.rollback;
|
||||
|
||||
import static com.android.server.rollback.RollbackData.rollbackStateFromString;
|
||||
import static com.android.server.rollback.RollbackData.rollbackStateToString;
|
||||
import static com.android.server.rollback.Rollback.rollbackStateFromString;
|
||||
import static com.android.server.rollback.Rollback.rollbackStateToString;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.pm.VersionedPackage;
|
||||
@@ -25,7 +25,7 @@ import android.content.rollback.PackageRollbackInfo;
|
||||
import android.content.rollback.PackageRollbackInfo.RestoreInfo;
|
||||
import android.content.rollback.RollbackInfo;
|
||||
import android.util.IntArray;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseLongArray;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
@@ -73,17 +73,17 @@ class RollbackStore {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the rollback data from persistent storage.
|
||||
* Reads the rollbacks from persistent storage.
|
||||
*/
|
||||
List<RollbackData> loadAllRollbackData() {
|
||||
List<RollbackData> rollbacks = new ArrayList<>();
|
||||
List<Rollback> loadRollbacks() {
|
||||
List<Rollback> rollbacks = new ArrayList<>();
|
||||
mRollbackDataDir.mkdirs();
|
||||
for (File rollbackDir : mRollbackDataDir.listFiles()) {
|
||||
if (rollbackDir.isDirectory()) {
|
||||
try {
|
||||
rollbacks.add(loadRollbackData(rollbackDir));
|
||||
rollbacks.add(loadRollback(rollbackDir));
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to read rollback data at " + rollbackDir, e);
|
||||
Slog.e(TAG, "Unable to read rollback at " + rollbackDir, e);
|
||||
removeFile(rollbackDir);
|
||||
}
|
||||
}
|
||||
@@ -191,21 +191,21 @@ class RollbackStore {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new RollbackData instance for a non-staged rollback with
|
||||
* Creates a new Rollback instance for a non-staged rollback with
|
||||
* backupDir assigned.
|
||||
*/
|
||||
RollbackData createNonStagedRollback(int rollbackId) {
|
||||
Rollback createNonStagedRollback(int rollbackId) {
|
||||
File backupDir = new File(mRollbackDataDir, Integer.toString(rollbackId));
|
||||
return new RollbackData(rollbackId, backupDir, -1);
|
||||
return new Rollback(rollbackId, backupDir, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new RollbackData instance for a staged rollback with
|
||||
* Creates a new Rollback instance for a staged rollback with
|
||||
* backupDir assigned.
|
||||
*/
|
||||
RollbackData createStagedRollback(int rollbackId, int stagedSessionId) {
|
||||
Rollback createStagedRollback(int rollbackId, int stagedSessionId) {
|
||||
File backupDir = new File(mRollbackDataDir, Integer.toString(rollbackId));
|
||||
return new RollbackData(rollbackId, backupDir, stagedSessionId);
|
||||
return new Rollback(rollbackId, backupDir, stagedSessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,10 +213,10 @@ class RollbackStore {
|
||||
* For packages containing splits, this method should be called for each
|
||||
* of the package's split apks in addition to the base apk.
|
||||
*/
|
||||
static void backupPackageCodePath(RollbackData data, String packageName, String codePath)
|
||||
static void backupPackageCodePath(Rollback rollback, String packageName, String codePath)
|
||||
throws IOException {
|
||||
File sourceFile = new File(codePath);
|
||||
File targetDir = new File(data.backupDir, packageName);
|
||||
File targetDir = new File(rollback.backupDir, packageName);
|
||||
targetDir.mkdirs();
|
||||
File targetFile = new File(targetDir, sourceFile.getName());
|
||||
|
||||
@@ -228,8 +228,8 @@ class RollbackStore {
|
||||
* Returns the apk or apex files backed up for the given package.
|
||||
* Includes the base apk and any splits. Returns null if none found.
|
||||
*/
|
||||
static File[] getPackageCodePaths(RollbackData data, String packageName) {
|
||||
File targetDir = new File(data.backupDir, packageName);
|
||||
static File[] getPackageCodePaths(Rollback rollback, String packageName) {
|
||||
File targetDir = new File(rollback.backupDir, packageName);
|
||||
File[] files = targetDir.listFiles();
|
||||
if (files == null || files.length == 0) {
|
||||
return null;
|
||||
@@ -241,27 +241,27 @@ class RollbackStore {
|
||||
* Deletes all backed up apks and apex files associated with the given
|
||||
* rollback.
|
||||
*/
|
||||
static void deletePackageCodePaths(RollbackData data) {
|
||||
for (PackageRollbackInfo info : data.info.getPackages()) {
|
||||
File targetDir = new File(data.backupDir, info.getPackageName());
|
||||
static void deletePackageCodePaths(Rollback rollback) {
|
||||
for (PackageRollbackInfo info : rollback.info.getPackages()) {
|
||||
File targetDir = new File(rollback.backupDir, info.getPackageName());
|
||||
removeFile(targetDir);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the rollback data to persistent storage.
|
||||
* Saves the given rollback to persistent storage.
|
||||
*/
|
||||
void saveRollbackData(RollbackData data) throws IOException {
|
||||
void saveRollback(Rollback rollback) throws IOException {
|
||||
try {
|
||||
JSONObject dataJson = new JSONObject();
|
||||
dataJson.put("info", rollbackInfoToJson(data.info));
|
||||
dataJson.put("timestamp", data.timestamp.toString());
|
||||
dataJson.put("stagedSessionId", data.stagedSessionId);
|
||||
dataJson.put("state", rollbackStateToString(data.state));
|
||||
dataJson.put("apkSessionId", data.apkSessionId);
|
||||
dataJson.put("restoreUserDataInProgress", data.restoreUserDataInProgress);
|
||||
dataJson.put("info", rollbackInfoToJson(rollback.info));
|
||||
dataJson.put("timestamp", rollback.timestamp.toString());
|
||||
dataJson.put("stagedSessionId", rollback.stagedSessionId);
|
||||
dataJson.put("state", rollbackStateToString(rollback.state));
|
||||
dataJson.put("apkSessionId", rollback.apkSessionId);
|
||||
dataJson.put("restoreUserDataInProgress", rollback.restoreUserDataInProgress);
|
||||
|
||||
PrintWriter pw = new PrintWriter(new File(data.backupDir, "rollback.json"));
|
||||
PrintWriter pw = new PrintWriter(new File(rollback.backupDir, "rollback.json"));
|
||||
pw.println(dataJson.toString());
|
||||
pw.close();
|
||||
} catch (JSONException e) {
|
||||
@@ -270,23 +270,23 @@ class RollbackStore {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all persistant storage associated with the given rollback data.
|
||||
* Removes all persistent storage associated with the given rollback.
|
||||
*/
|
||||
void deleteRollbackData(RollbackData data) {
|
||||
removeFile(data.backupDir);
|
||||
void deleteRollback(Rollback rollback) {
|
||||
removeFile(rollback.backupDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the metadata for a rollback from the given directory.
|
||||
* @throws IOException in case of error reading the data.
|
||||
*/
|
||||
private static RollbackData loadRollbackData(File backupDir) throws IOException {
|
||||
private static Rollback loadRollback(File backupDir) throws IOException {
|
||||
try {
|
||||
File rollbackJsonFile = new File(backupDir, "rollback.json");
|
||||
JSONObject dataJson = new JSONObject(
|
||||
IoUtils.readFileAsString(rollbackJsonFile.getAbsolutePath()));
|
||||
|
||||
return new RollbackData(
|
||||
return new Rollback(
|
||||
rollbackInfoFromJson(dataJson.getJSONObject("info")),
|
||||
backupDir,
|
||||
Instant.parse(dataJson.getString("timestamp")),
|
||||
@@ -319,13 +319,14 @@ class RollbackStore {
|
||||
|
||||
IntArray pendingBackups = info.getPendingBackups();
|
||||
List<RestoreInfo> pendingRestores = info.getPendingRestores();
|
||||
IntArray installedUsers = info.getInstalledUsers();
|
||||
IntArray snapshottedUsers = info.getSnapshottedUsers();
|
||||
json.put("pendingBackups", convertToJsonArray(pendingBackups));
|
||||
json.put("pendingRestores", convertToJsonArray(pendingRestores));
|
||||
|
||||
json.put("isApex", info.isApex());
|
||||
|
||||
json.put("installedUsers", convertToJsonArray(installedUsers));
|
||||
// Field is named 'installedUsers' for legacy reasons.
|
||||
json.put("installedUsers", convertToJsonArray(snapshottedUsers));
|
||||
json.put("ceSnapshotInodes", ceSnapshotInodesToJson(info.getCeSnapshotInodes()));
|
||||
|
||||
return json;
|
||||
@@ -345,12 +346,13 @@ class RollbackStore {
|
||||
|
||||
final boolean isApex = json.getBoolean("isApex");
|
||||
|
||||
final IntArray installedUsers = convertToIntArray(json.getJSONArray("installedUsers"));
|
||||
// Field is named 'installedUsers' for legacy reasons.
|
||||
final IntArray snapshottedUsers = convertToIntArray(json.getJSONArray("installedUsers"));
|
||||
final SparseLongArray ceSnapshotInodes = ceSnapshotInodesFromJson(
|
||||
json.getJSONArray("ceSnapshotInodes"));
|
||||
|
||||
return new PackageRollbackInfo(versionRolledBackFrom, versionRolledBackTo,
|
||||
pendingBackups, pendingRestores, isApex, installedUsers, ceSnapshotInodes);
|
||||
pendingBackups, pendingRestores, isApex, snapshottedUsers, ceSnapshotInodes);
|
||||
}
|
||||
|
||||
private static JSONArray versionedPackagesToJson(List<VersionedPackage> packages)
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
{
|
||||
"presubmit": [
|
||||
{
|
||||
"name": "RollbackTest"
|
||||
},
|
||||
{
|
||||
"name": "StagedRollbackTest"
|
||||
},
|
||||
{
|
||||
"name": "FrameworksServicesTests",
|
||||
"options": [
|
||||
@@ -21,6 +15,9 @@
|
||||
},
|
||||
{
|
||||
"path": "cts/hostsidetests/rollback"
|
||||
},
|
||||
{
|
||||
"path": "frameworks/base/tests/RollbackTest"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.server.rollback;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
@@ -58,8 +59,8 @@ public class AppDataRollbackHelperTest {
|
||||
// All users are unlocked so we should snapshot data for them.
|
||||
doReturn(true).when(helper).isUserCredentialLocked(eq(10));
|
||||
doReturn(true).when(helper).isUserCredentialLocked(eq(11));
|
||||
PackageRollbackInfo info = createPackageRollbackInfo("com.foo.bar", new int[]{10, 11});
|
||||
helper.snapshotAppData(5, info);
|
||||
PackageRollbackInfo info = createPackageRollbackInfo("com.foo.bar");
|
||||
helper.snapshotAppData(5, info, new int[]{10, 11});
|
||||
|
||||
assertEquals(2, info.getPendingBackups().size());
|
||||
assertEquals(10, info.getPendingBackups().get(0));
|
||||
@@ -79,8 +80,8 @@ public class AppDataRollbackHelperTest {
|
||||
doReturn(true).when(helper).isUserCredentialLocked(eq(11));
|
||||
when(installer.snapshotAppData(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(239L);
|
||||
|
||||
PackageRollbackInfo info2 = createPackageRollbackInfo("com.foo.bar", new int[]{10, 11});
|
||||
helper.snapshotAppData(7, info2);
|
||||
PackageRollbackInfo info2 = createPackageRollbackInfo("com.foo.bar");
|
||||
helper.snapshotAppData(7, info2, new int[]{10, 11});
|
||||
assertEquals(1, info2.getPendingBackups().size());
|
||||
assertEquals(11, info2.getPendingBackups().get(0));
|
||||
|
||||
@@ -234,22 +235,22 @@ public class AppDataRollbackHelperTest {
|
||||
wasRecentlyRestored.getPendingRestores().add(
|
||||
new RestoreInfo(73 /* userId */, 239 /* appId*/, "seInfo"));
|
||||
|
||||
RollbackData dataWithPendingBackup = new RollbackData(101, new File("/does/not/exist"), -1);
|
||||
Rollback dataWithPendingBackup = new Rollback(101, new File("/does/not/exist"), -1);
|
||||
dataWithPendingBackup.info.getPackages().add(pendingBackup);
|
||||
|
||||
RollbackData dataWithRecentRestore = new RollbackData(17239, new File("/does/not/exist"),
|
||||
Rollback dataWithRecentRestore = new Rollback(17239, new File("/does/not/exist"),
|
||||
-1);
|
||||
dataWithRecentRestore.info.getPackages().add(wasRecentlyRestored);
|
||||
|
||||
RollbackData dataForDifferentUser = new RollbackData(17239, new File("/does/not/exist"),
|
||||
Rollback dataForDifferentUser = new Rollback(17239, new File("/does/not/exist"),
|
||||
-1);
|
||||
dataForDifferentUser.info.getPackages().add(ignoredInfo);
|
||||
|
||||
RollbackData dataForRestore = new RollbackData(17239, new File("/does/not/exist"), -1);
|
||||
Rollback dataForRestore = new Rollback(17239, new File("/does/not/exist"), -1);
|
||||
dataForRestore.info.getPackages().add(pendingRestore);
|
||||
dataForRestore.info.getPackages().add(wasRecentlyRestored);
|
||||
|
||||
Set<RollbackData> changed = helper.commitPendingBackupAndRestoreForUser(37,
|
||||
Set<Rollback> changed = helper.commitPendingBackupAndRestoreForUser(37,
|
||||
Arrays.asList(dataWithPendingBackup, dataWithRecentRestore, dataForDifferentUser,
|
||||
dataForRestore));
|
||||
InOrder inOrder = Mockito.inOrder(installer);
|
||||
@@ -264,7 +265,7 @@ public class AppDataRollbackHelperTest {
|
||||
assertEquals(-1, pendingBackup.getPendingBackups().indexOf(37));
|
||||
assertEquals(53, pendingBackup.getCeSnapshotInodes().get(37));
|
||||
|
||||
// Check that changed returns correct RollbackData.
|
||||
// Check that changed returns correct Rollback.
|
||||
assertEquals(3, changed.size());
|
||||
assertTrue(changed.contains(dataWithPendingBackup));
|
||||
assertTrue(changed.contains(dataWithRecentRestore));
|
||||
@@ -278,4 +279,15 @@ public class AppDataRollbackHelperTest {
|
||||
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void snapshotAddDataSavesSnapshottedUsersToInfo() {
|
||||
Installer installer = mock(Installer.class);
|
||||
AppDataRollbackHelper helper = new AppDataRollbackHelper(installer);
|
||||
|
||||
PackageRollbackInfo info = createPackageRollbackInfo("com.foo.bar");
|
||||
helper.snapshotAppData(5, info, new int[]{10, 11});
|
||||
|
||||
assertArrayEquals(info.getSnapshottedUsers().toArray(), new int[]{10, 11});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,88 +12,12 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
android_test_helper_app {
|
||||
name: "RollbackTestAppAv1",
|
||||
manifest: "TestApp/Av1.xml",
|
||||
sdk_version: "current",
|
||||
srcs: ["TestApp/src/**/*.java"],
|
||||
resource_dirs: ["TestApp/res_v1"],
|
||||
}
|
||||
|
||||
android_test_helper_app {
|
||||
name: "RollbackTestAppAv2",
|
||||
manifest: "TestApp/Av2.xml",
|
||||
sdk_version: "current",
|
||||
srcs: ["TestApp/src/**/*.java"],
|
||||
resource_dirs: ["TestApp/res_v2"],
|
||||
}
|
||||
|
||||
android_test_helper_app {
|
||||
name: "RollbackTestAppAv3",
|
||||
manifest: "TestApp/Av3.xml",
|
||||
sdk_version: "current",
|
||||
srcs: ["TestApp/src/**/*.java"],
|
||||
resource_dirs: ["TestApp/res_v3"],
|
||||
}
|
||||
|
||||
android_test_helper_app {
|
||||
name: "RollbackTestAppACrashingV2",
|
||||
manifest: "TestApp/ACrashingV2.xml",
|
||||
sdk_version: "current",
|
||||
srcs: ["TestApp/src/**/*.java"],
|
||||
resource_dirs: ["TestApp/res_v2"],
|
||||
}
|
||||
|
||||
android_test_helper_app {
|
||||
name: "RollbackTestAppBv1",
|
||||
manifest: "TestApp/Bv1.xml",
|
||||
sdk_version: "current",
|
||||
srcs: ["TestApp/src/**/*.java"],
|
||||
resource_dirs: ["TestApp/res_v1"],
|
||||
}
|
||||
|
||||
android_test_helper_app {
|
||||
name: "RollbackTestAppBv2",
|
||||
manifest: "TestApp/Bv2.xml",
|
||||
sdk_version: "current",
|
||||
srcs: ["TestApp/src/**/*.java"],
|
||||
resource_dirs: ["TestApp/res_v2"],
|
||||
}
|
||||
|
||||
android_test_helper_app {
|
||||
name: "RollbackTestAppASplitV1",
|
||||
manifest: "TestApp/Av1.xml",
|
||||
sdk_version: "current",
|
||||
srcs: ["TestApp/src/**/*.java"],
|
||||
resource_dirs: ["TestApp/res_v1"],
|
||||
package_splits: ["anydpi"],
|
||||
}
|
||||
|
||||
android_test_helper_app {
|
||||
name: "RollbackTestAppASplitV2",
|
||||
manifest: "TestApp/Av2.xml",
|
||||
sdk_version: "current",
|
||||
srcs: ["TestApp/src/**/*.java"],
|
||||
resource_dirs: ["TestApp/res_v2"],
|
||||
package_splits: ["anydpi"],
|
||||
}
|
||||
|
||||
android_test {
|
||||
name: "RollbackTest",
|
||||
manifest: "RollbackTest/AndroidManifest.xml",
|
||||
srcs: ["RollbackTest/src/**/*.java"],
|
||||
static_libs: ["androidx.test.rules"],
|
||||
static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"],
|
||||
test_suites: ["general-tests"],
|
||||
java_resources: [
|
||||
":RollbackTestAppAv1",
|
||||
":RollbackTestAppAv2",
|
||||
":RollbackTestAppAv3",
|
||||
":RollbackTestAppACrashingV2",
|
||||
":RollbackTestAppBv1",
|
||||
":RollbackTestAppBv2",
|
||||
":RollbackTestAppASplitV1",
|
||||
":RollbackTestAppASplitV2",
|
||||
],
|
||||
test_config: "RollbackTest.xml",
|
||||
// TODO: sdk_version: "test_current" when Intent#resolveSystemservice is TestApi
|
||||
}
|
||||
@@ -105,3 +29,11 @@ java_test_host {
|
||||
test_suites: ["general-tests"],
|
||||
test_config: "StagedRollbackTest.xml",
|
||||
}
|
||||
|
||||
java_test_host {
|
||||
name: "MultiUserRollbackTest",
|
||||
srcs: ["MultiUserRollbackTest/src/**/*.java"],
|
||||
libs: ["tradefed"],
|
||||
test_suites: ["general-tests"],
|
||||
test_config: "MultiUserRollbackTest.xml",
|
||||
}
|
||||
|
||||
@@ -13,8 +13,12 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<integer name="app_version">2</integer>
|
||||
<integer name="split_version">0</integer>
|
||||
</resources>
|
||||
<configuration description="Runs rollback tests for multiple users">
|
||||
<option name="test-suite-tag" value="MultiUserRollbackTest" />
|
||||
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
|
||||
<option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
|
||||
</target_preparer>
|
||||
<test class="com.android.tradefed.testtype.HostTest" >
|
||||
<option name="class" value="com.android.tests.rollback.host.MultiUserRollbackTest" />
|
||||
</test>
|
||||
</configuration>
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.tests.rollback.host;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
|
||||
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Runs rollback tests for multiple users.
|
||||
*/
|
||||
@RunWith(DeviceJUnit4ClassRunner.class)
|
||||
public class MultiUserRollbackTest extends BaseHostJUnit4Test {
|
||||
// The user that was running originally when the test starts.
|
||||
private int mOriginalUserId;
|
||||
private int mSecondaryUserId = -1;
|
||||
private static final long SWITCH_USER_COMPLETED_NUMBER_OF_POLLS = 60;
|
||||
private static final long SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS = 1000;
|
||||
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
getDevice().switchUser(mOriginalUserId);
|
||||
getDevice().executeShellCommand("pm uninstall com.android.cts.install.lib.testapp.A");
|
||||
removeSecondaryUserIfNecessary();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
mOriginalUserId = getDevice().getCurrentUser();
|
||||
installPackageAsUser("RollbackTest.apk", true, mOriginalUserId);
|
||||
createAndSwitchToSecondaryUserIfNecessary();
|
||||
installPackageAsUser("RollbackTest.apk", true, mSecondaryUserId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicForSecondaryUser() throws Exception {
|
||||
runPhaseForUsers("testBasic", mSecondaryUserId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleUsers() throws Exception {
|
||||
runPhaseForUsers("testMultipleUsersInstallV1", mOriginalUserId, mSecondaryUserId);
|
||||
runPhaseForUsers("testMultipleUsersUpgradeToV2", mOriginalUserId);
|
||||
runPhaseForUsers("testMultipleUsersUpdateUserData", mOriginalUserId, mSecondaryUserId);
|
||||
switchToUser(mOriginalUserId);
|
||||
getDevice().executeShellCommand("pm rollback-app com.android.cts.install.lib.testapp.A");
|
||||
runPhaseForUsers("testMultipleUsersVerifyUserdataRollback", mOriginalUserId,
|
||||
mSecondaryUserId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the phase for the given user ids, in the order they are given.
|
||||
*/
|
||||
private void runPhaseForUsers(String phase, int... userIds) throws Exception {
|
||||
for (int userId: userIds) {
|
||||
switchToUser(userId);
|
||||
assertTrue(runDeviceTests("com.android.tests.rollback",
|
||||
"com.android.tests.rollback.MultiUserRollbackTest",
|
||||
phase));
|
||||
}
|
||||
}
|
||||
|
||||
private void removeSecondaryUserIfNecessary() throws Exception {
|
||||
if (mSecondaryUserId != -1) {
|
||||
getDevice().removeUser(mSecondaryUserId);
|
||||
mSecondaryUserId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private void createAndSwitchToSecondaryUserIfNecessary() throws Exception {
|
||||
if (mSecondaryUserId == -1) {
|
||||
mOriginalUserId = getDevice().getCurrentUser();
|
||||
mSecondaryUserId = getDevice().createUser("MultiUserRollbackTest_User"
|
||||
+ System.currentTimeMillis());
|
||||
switchToUser(mSecondaryUserId);
|
||||
}
|
||||
}
|
||||
|
||||
private void switchToUser(int userId) throws Exception {
|
||||
if (getDevice().getCurrentUser() == userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
assertTrue(getDevice().switchUser(userId));
|
||||
for (int i = 0; i < SWITCH_USER_COMPLETED_NUMBER_OF_POLLS; ++i) {
|
||||
String userState = getDevice().executeShellCommand("am get-started-user-state "
|
||||
+ userId);
|
||||
if (userState.contains("RUNNING_UNLOCKED")) {
|
||||
return;
|
||||
}
|
||||
Thread.sleep(SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS);
|
||||
}
|
||||
fail("User switch to user " + userId + " timed out");
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,9 @@
|
||||
<option name="package" value="com.android.tests.rollback" />
|
||||
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
|
||||
|
||||
<!-- Exclude the StagedRollbackTest tests, which needs to be specially
|
||||
driven from the StagedRollbackTest host test -->
|
||||
<!-- Exclude the StagedRollbackTest and MultiUserRollbackTest tests, which need to be
|
||||
specially driven from the StagedRollbackTest and MultiUserRollbackTest host test -->
|
||||
<option name="exclude-filter" value="com.android.tests.rollback.StagedRollbackTest" />
|
||||
<option name="exclude-filter" value="com.android.tests.rollback.MultiUserRollbackTest" />
|
||||
</test>
|
||||
</configuration>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
package="com.android.tests.rollback" >
|
||||
|
||||
<application>
|
||||
<receiver android:name="com.android.tests.rollback.LocalIntentSender"
|
||||
<receiver android:name="com.android.cts.install.lib.LocalIntentSender"
|
||||
android:exported="true" />
|
||||
<uses-library android:name="android.test.runner" />
|
||||
</application>
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.tests.rollback;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
/**
|
||||
* Make IntentSender that sends intent locally.
|
||||
*/
|
||||
public class LocalIntentSender extends BroadcastReceiver {
|
||||
|
||||
private static final String TAG = "RollbackTest";
|
||||
|
||||
private static final BlockingQueue<Intent> sIntentSenderResults = new LinkedBlockingQueue<>();
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
sIntentSenderResults.add(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a LocalIntentSender.
|
||||
*/
|
||||
static IntentSender getIntentSender() {
|
||||
Context context = InstrumentationRegistry.getContext();
|
||||
Intent intent = new Intent(context, LocalIntentSender.class);
|
||||
PendingIntent pending = PendingIntent.getBroadcast(context, 0, intent, 0);
|
||||
return pending.getIntentSender();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the most recent Intent sent by a LocalIntentSender.
|
||||
*/
|
||||
static Intent getIntentSenderResult() throws InterruptedException {
|
||||
return sIntentSenderResults.take();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.tests.rollback;
|
||||
|
||||
import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat;
|
||||
import static com.android.cts.rollback.lib.RollbackUtils.getUniqueRollbackInfoForPackage;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.rollback.RollbackInfo;
|
||||
import android.content.rollback.RollbackManager;
|
||||
|
||||
import com.android.cts.install.lib.Install;
|
||||
import com.android.cts.install.lib.InstallUtils;
|
||||
import com.android.cts.install.lib.TestApp;
|
||||
import com.android.cts.rollback.lib.Rollback;
|
||||
import com.android.cts.rollback.lib.RollbackUtils;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class MultiUserRollbackTest {
|
||||
|
||||
@Before
|
||||
public void adoptShellPermissions() {
|
||||
InstallUtils.adoptShellPermissionIdentity(
|
||||
Manifest.permission.INSTALL_PACKAGES,
|
||||
Manifest.permission.DELETE_PACKAGES,
|
||||
Manifest.permission.TEST_MANAGE_ROLLBACKS,
|
||||
Manifest.permission.MANAGE_ROLLBACKS);
|
||||
}
|
||||
|
||||
@After
|
||||
public void dropShellPermissions() {
|
||||
InstallUtils.dropShellPermissionIdentity();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasic() throws Exception {
|
||||
new RollbackTest().testBasic();
|
||||
}
|
||||
|
||||
/**
|
||||
* Install version 1 of the test app. This method is run for both users.
|
||||
*/
|
||||
@Test
|
||||
public void testMultipleUsersInstallV1() throws Exception {
|
||||
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
|
||||
Install.single(TestApp.A1).commit();
|
||||
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
|
||||
InstallUtils.processUserData(TestApp.A);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade the test app to version 2. This method should only run once as the system user,
|
||||
* and will update the app for both users.
|
||||
*/
|
||||
@Test
|
||||
public void testMultipleUsersUpgradeToV2() throws Exception {
|
||||
RollbackManager rm = RollbackUtils.getRollbackManager();
|
||||
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
|
||||
Install.single(TestApp.A2).setEnableRollback().commit();
|
||||
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
|
||||
RollbackInfo rollback = getUniqueRollbackInfoForPackage(
|
||||
rm.getAvailableRollbacks(), TestApp.A);
|
||||
assertThat(rollback).isNotNull();
|
||||
assertThat(rollback).packagesContainsExactly(
|
||||
Rollback.from(TestApp.A2).to(TestApp.A1));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is run for both users. Assert that the test app has upgraded for both users, and
|
||||
* update their userdata to reflect this new version.
|
||||
*/
|
||||
@Test
|
||||
public void testMultipleUsersUpdateUserData() {
|
||||
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
|
||||
InstallUtils.processUserData(TestApp.A);
|
||||
}
|
||||
|
||||
/**
|
||||
* The system will have rolled back the test app at this stage. Verify that the rollback has
|
||||
* taken place, and that the userdata has been correctly rolled back. This method is run for
|
||||
* both users.
|
||||
*/
|
||||
@Test
|
||||
public void testMultipleUsersVerifyUserdataRollback() {
|
||||
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
|
||||
InstallUtils.processUserData(TestApp.A);
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.tests.rollback;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A broadcast receiver that can be used to get
|
||||
* ACTION_ROLLBACK_COMMITTED broadcasts.
|
||||
*/
|
||||
class RollbackBroadcastReceiver extends BroadcastReceiver {
|
||||
|
||||
private static final String TAG = "RollbackTest";
|
||||
|
||||
private final BlockingQueue<Intent> mRollbackBroadcasts = new LinkedBlockingQueue<>();
|
||||
|
||||
/**
|
||||
* Creates a RollbackBroadcastReceiver and registers it with the given
|
||||
* context.
|
||||
*/
|
||||
RollbackBroadcastReceiver() {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_ROLLBACK_COMMITTED);
|
||||
InstrumentationRegistry.getContext().registerReceiver(this, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.i(TAG, "Received rollback broadcast intent");
|
||||
mRollbackBroadcasts.add(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Polls for at most the given amount of time for the next rollback
|
||||
* broadcast.
|
||||
*/
|
||||
Intent poll(long timeout, TimeUnit unit) throws InterruptedException {
|
||||
return mRollbackBroadcasts.poll(timeout, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits forever for the next rollback broadcast.
|
||||
*/
|
||||
Intent take() throws InterruptedException {
|
||||
return mRollbackBroadcasts.take();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this broadcast receiver.
|
||||
*/
|
||||
void unregister() {
|
||||
InstrumentationRegistry.getContext().unregisterReceiver(this);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,547 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.tests.rollback;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.AlarmManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.VersionedPackage;
|
||||
import android.content.rollback.PackageRollbackInfo;
|
||||
import android.content.rollback.RollbackInfo;
|
||||
import android.content.rollback.RollbackManager;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
|
||||
/**
|
||||
* Utilities to facilitate testing rollbacks.
|
||||
*/
|
||||
class RollbackTestUtils {
|
||||
|
||||
private static final String TAG = "RollbackTest";
|
||||
|
||||
static RollbackManager getRollbackManager() {
|
||||
Context context = InstrumentationRegistry.getContext();
|
||||
RollbackManager rm = (RollbackManager) context.getSystemService(Context.ROLLBACK_SERVICE);
|
||||
if (rm == null) {
|
||||
throw new AssertionError("Failed to get RollbackManager");
|
||||
}
|
||||
return rm;
|
||||
}
|
||||
|
||||
private static void setTime(long millis) {
|
||||
Context context = InstrumentationRegistry.getContext();
|
||||
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
am.setTime(millis);
|
||||
}
|
||||
|
||||
static void forwardTimeBy(long offsetMillis) {
|
||||
setTime(System.currentTimeMillis() + offsetMillis);
|
||||
Log.i(TAG, "Forwarded time on device by " + offsetMillis + " millis");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version of the given package installed on device.
|
||||
* Returns -1 if the package is not currently installed.
|
||||
*/
|
||||
static long getInstalledVersion(String packageName) {
|
||||
PackageInfo pi = getPackageInfo(packageName);
|
||||
if (pi == null) {
|
||||
return -1;
|
||||
} else {
|
||||
return pi.getLongVersionCode();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isSystemAppWithoutUpdate(String packageName) {
|
||||
PackageInfo pi = getPackageInfo(packageName);
|
||||
if (pi == null) {
|
||||
return false;
|
||||
} else {
|
||||
return ((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0)
|
||||
&& ((pi.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static PackageInfo getPackageInfo(String packageName) {
|
||||
Context context = InstrumentationRegistry.getContext();
|
||||
PackageManager pm = context.getPackageManager();
|
||||
try {
|
||||
return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertStatusSuccess(Intent result) {
|
||||
int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
|
||||
PackageInstaller.STATUS_FAILURE);
|
||||
if (status == -1) {
|
||||
throw new AssertionError("PENDING USER ACTION");
|
||||
} else if (status > 0) {
|
||||
String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
|
||||
throw new AssertionError(message == null ? "UNKNOWN FAILURE" : message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstalls the given package.
|
||||
* Does nothing if the package is not installed.
|
||||
* @throws AssertionError if package can't be uninstalled.
|
||||
*/
|
||||
static void uninstall(String packageName) throws InterruptedException, IOException {
|
||||
// No need to uninstall if the package isn't installed or is installed on /system.
|
||||
if (getInstalledVersion(packageName) == -1 || isSystemAppWithoutUpdate(packageName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Context context = InstrumentationRegistry.getContext();
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
PackageInstaller packageInstaller = packageManager.getPackageInstaller();
|
||||
packageInstaller.uninstall(packageName, LocalIntentSender.getIntentSender());
|
||||
assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit the given rollback.
|
||||
* @throws AssertionError if the rollback fails.
|
||||
*/
|
||||
static void rollback(int rollbackId, VersionedPackage... causePackages)
|
||||
throws InterruptedException {
|
||||
RollbackManager rm = getRollbackManager();
|
||||
rm.commitRollback(rollbackId, Arrays.asList(causePackages),
|
||||
LocalIntentSender.getIntentSender());
|
||||
Intent result = LocalIntentSender.getIntentSenderResult();
|
||||
int status = result.getIntExtra(RollbackManager.EXTRA_STATUS,
|
||||
RollbackManager.STATUS_FAILURE);
|
||||
if (status != RollbackManager.STATUS_SUCCESS) {
|
||||
String message = result.getStringExtra(RollbackManager.EXTRA_STATUS_MESSAGE);
|
||||
throw new AssertionError(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the apk with the given name.
|
||||
*
|
||||
* @param resourceName name of class loader resource for the apk to
|
||||
* install.
|
||||
* @param enableRollback if rollback should be enabled.
|
||||
* @throws AssertionError if the installation fails.
|
||||
*/
|
||||
static void install(String resourceName, boolean enableRollback)
|
||||
throws InterruptedException, IOException {
|
||||
installSplit(enableRollback, resourceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the apk with the given name and its splits.
|
||||
*
|
||||
* @param enableRollback if rollback should be enabled.
|
||||
* @param resourceNames names of class loader resources for the apk and
|
||||
* its splits to install.
|
||||
* @throws AssertionError if the installation fails.
|
||||
*/
|
||||
static void installSplit(boolean enableRollback, String... resourceNames)
|
||||
throws InterruptedException, IOException {
|
||||
Context context = InstrumentationRegistry.getContext();
|
||||
PackageInstaller.Session session = null;
|
||||
PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
|
||||
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
|
||||
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
|
||||
params.setEnableRollback(enableRollback);
|
||||
int sessionId = packageInstaller.createSession(params);
|
||||
session = packageInstaller.openSession(sessionId);
|
||||
|
||||
ClassLoader loader = RollbackTest.class.getClassLoader();
|
||||
for (String resourceName : resourceNames) {
|
||||
try (OutputStream packageInSession = session.openWrite(resourceName, 0, -1);
|
||||
InputStream is = loader.getResourceAsStream(resourceName);) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int n;
|
||||
while ((n = is.read(buffer)) >= 0) {
|
||||
packageInSession.write(buffer, 0, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Commit the session (this will start the installation workflow).
|
||||
session.commit(LocalIntentSender.getIntentSender());
|
||||
assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
|
||||
}
|
||||
|
||||
/** Launches {@code packageName} with {@link Intent#ACTION_MAIN}. */
|
||||
private static void launchPackage(String packageName)
|
||||
throws InterruptedException, IOException {
|
||||
Context context = InstrumentationRegistry.getContext();
|
||||
Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||
intent.setPackage(packageName);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the APKs or APEXs with the given resource names as an atomic
|
||||
* set. A resource is assumed to be an APEX if it has the .apex extension.
|
||||
* <p>
|
||||
* In case of staged installs, this function will return succesfully after
|
||||
* the staged install has been committed and is ready for the device to
|
||||
* reboot.
|
||||
*
|
||||
* @param staged if the rollback should be staged.
|
||||
* @param enableRollback if rollback should be enabled.
|
||||
* @param resourceNames names of the class loader resource for the apks to
|
||||
* install.
|
||||
* @throws AssertionError if the installation fails.
|
||||
*/
|
||||
private static void install(boolean staged, boolean enableRollback,
|
||||
String... resourceNames) throws InterruptedException, IOException {
|
||||
Context context = InstrumentationRegistry.getContext();
|
||||
PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
|
||||
|
||||
PackageInstaller.SessionParams multiPackageParams = new PackageInstaller.SessionParams(
|
||||
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
|
||||
multiPackageParams.setMultiPackage();
|
||||
if (staged) {
|
||||
multiPackageParams.setStaged();
|
||||
}
|
||||
// TODO: Do we set this on the parent params, the child params, or
|
||||
// both?
|
||||
multiPackageParams.setEnableRollback(enableRollback);
|
||||
int multiPackageId = packageInstaller.createSession(multiPackageParams);
|
||||
PackageInstaller.Session multiPackage = packageInstaller.openSession(multiPackageId);
|
||||
|
||||
for (String resourceName : resourceNames) {
|
||||
PackageInstaller.Session session = null;
|
||||
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
|
||||
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
|
||||
if (staged) {
|
||||
params.setStaged();
|
||||
}
|
||||
if (resourceName.endsWith(".apex")) {
|
||||
params.setInstallAsApex();
|
||||
}
|
||||
params.setEnableRollback(enableRollback);
|
||||
int sessionId = packageInstaller.createSession(params);
|
||||
session = packageInstaller.openSession(sessionId);
|
||||
|
||||
ClassLoader loader = RollbackTest.class.getClassLoader();
|
||||
try (OutputStream packageInSession = session.openWrite(resourceName, 0, -1);
|
||||
InputStream is = loader.getResourceAsStream(resourceName);) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int n;
|
||||
while ((n = is.read(buffer)) >= 0) {
|
||||
packageInSession.write(buffer, 0, n);
|
||||
}
|
||||
}
|
||||
multiPackage.addChildSessionId(sessionId);
|
||||
}
|
||||
|
||||
// Commit the session (this will start the installation workflow).
|
||||
multiPackage.commit(LocalIntentSender.getIntentSender());
|
||||
assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
|
||||
|
||||
if (staged) {
|
||||
waitForSessionReady(multiPackageId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the apks with the given resource names as an atomic set.
|
||||
*
|
||||
* @param enableRollback if rollback should be enabled.
|
||||
* @param resourceNames names of the class loader resource for the apks to
|
||||
* install.
|
||||
* @throws AssertionError if the installation fails.
|
||||
*/
|
||||
static void installMultiPackage(boolean enableRollback, String... resourceNames)
|
||||
throws InterruptedException, IOException {
|
||||
install(false, enableRollback, resourceNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the APKs or APEXs with the given resource names as a staged
|
||||
* atomic set. A resource is assumed to be an APEX if it has the .apex
|
||||
* extension.
|
||||
*
|
||||
* @param enableRollback if rollback should be enabled.
|
||||
* @param resourceNames names of the class loader resource for the apks to
|
||||
* install.
|
||||
* @throws AssertionError if the installation fails.
|
||||
*/
|
||||
static void installStaged(boolean enableRollback, String... resourceNames)
|
||||
throws InterruptedException, IOException {
|
||||
install(true, enableRollback, resourceNames);
|
||||
}
|
||||
|
||||
static void adoptShellPermissionIdentity(String... permissions) {
|
||||
InstrumentationRegistry
|
||||
.getInstrumentation()
|
||||
.getUiAutomation()
|
||||
.adoptShellPermissionIdentity(permissions);
|
||||
}
|
||||
|
||||
static void dropShellPermissionIdentity() {
|
||||
InstrumentationRegistry
|
||||
.getInstrumentation()
|
||||
.getUiAutomation()
|
||||
.dropShellPermissionIdentity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RollbackInfo with a given package in the list of rollbacks.
|
||||
* Throws an assertion failure if there is more than one such rollback
|
||||
* info. Returns null if there are no such rollback infos.
|
||||
*/
|
||||
static RollbackInfo getUniqueRollbackInfoForPackage(List<RollbackInfo> rollbacks,
|
||||
String packageName) {
|
||||
RollbackInfo found = null;
|
||||
for (RollbackInfo rollback : rollbacks) {
|
||||
for (PackageRollbackInfo info : rollback.getPackages()) {
|
||||
if (packageName.equals(info.getPackageName())) {
|
||||
assertNull(found);
|
||||
found = rollback;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given PackageRollbackInfo has the expected package
|
||||
* name and versions.
|
||||
*/
|
||||
static void assertPackageRollbackInfoEquals(String packageName,
|
||||
long versionRolledBackFrom, long versionRolledBackTo,
|
||||
PackageRollbackInfo info) {
|
||||
assertEquals(packageName, info.getPackageName());
|
||||
assertEquals(packageName, info.getVersionRolledBackFrom().getPackageName());
|
||||
assertEquals(versionRolledBackFrom, info.getVersionRolledBackFrom().getLongVersionCode());
|
||||
assertEquals(packageName, info.getVersionRolledBackTo().getPackageName());
|
||||
assertEquals(versionRolledBackTo, info.getVersionRolledBackTo().getLongVersionCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given RollbackInfo has the given packages with expected
|
||||
* package names and all are rolled to and from the same given versions.
|
||||
*/
|
||||
static void assertRollbackInfoEquals(String[] packageNames,
|
||||
long versionRolledBackFrom, long versionRolledBackTo,
|
||||
RollbackInfo info, VersionedPackage... causePackages) {
|
||||
assertNotNull(info);
|
||||
assertEquals(packageNames.length, info.getPackages().size());
|
||||
int foundPackages = 0;
|
||||
for (String packageName : packageNames) {
|
||||
for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
|
||||
if (packageName.equals(pkgRollbackInfo.getPackageName())) {
|
||||
foundPackages++;
|
||||
assertPackageRollbackInfoEquals(packageName, versionRolledBackFrom,
|
||||
versionRolledBackTo, pkgRollbackInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(packageNames.length, foundPackages);
|
||||
assertEquals(causePackages.length, info.getCausePackages().size());
|
||||
for (int i = 0; i < causePackages.length; ++i) {
|
||||
assertEquals(causePackages[i].getPackageName(),
|
||||
info.getCausePackages().get(i).getPackageName());
|
||||
assertEquals(causePackages[i].getLongVersionCode(),
|
||||
info.getCausePackages().get(i).getLongVersionCode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given RollbackInfo has a single package with expected
|
||||
* package name and versions.
|
||||
*/
|
||||
static void assertRollbackInfoEquals(String packageName,
|
||||
long versionRolledBackFrom, long versionRolledBackTo,
|
||||
RollbackInfo info, VersionedPackage... causePackages) {
|
||||
String[] packageNames = {packageName};
|
||||
assertRollbackInfoEquals(packageNames, versionRolledBackFrom, versionRolledBackTo, info,
|
||||
causePackages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given session to be marked as ready.
|
||||
* Throws an assertion if the session fails.
|
||||
*/
|
||||
static void waitForSessionReady(int sessionId) {
|
||||
BlockingQueue<PackageInstaller.SessionInfo> sessionStatus = new LinkedBlockingQueue<>();
|
||||
BroadcastReceiver sessionUpdatedReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
PackageInstaller.SessionInfo info =
|
||||
intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
|
||||
if (info != null && info.getSessionId() == sessionId) {
|
||||
if (info.isStagedSessionReady() || info.isStagedSessionFailed()) {
|
||||
try {
|
||||
sessionStatus.put(info);
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "Failed to put session info.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
IntentFilter sessionUpdatedFilter =
|
||||
new IntentFilter(PackageInstaller.ACTION_SESSION_UPDATED);
|
||||
|
||||
Context context = InstrumentationRegistry.getContext();
|
||||
context.registerReceiver(sessionUpdatedReceiver, sessionUpdatedFilter);
|
||||
|
||||
PackageInstaller installer = context.getPackageManager().getPackageInstaller();
|
||||
PackageInstaller.SessionInfo info = installer.getSessionInfo(sessionId);
|
||||
|
||||
try {
|
||||
if (info.isStagedSessionReady() || info.isStagedSessionFailed()) {
|
||||
sessionStatus.put(info);
|
||||
}
|
||||
|
||||
info = sessionStatus.take();
|
||||
context.unregisterReceiver(sessionUpdatedReceiver);
|
||||
if (info.isStagedSessionFailed()) {
|
||||
throw new AssertionError(info.getStagedSessionErrorMessage());
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final String NO_RESPONSE = "NO RESPONSE";
|
||||
|
||||
/**
|
||||
* Calls into the test app to process user data.
|
||||
* Asserts if the user data could not be processed or was version
|
||||
* incompatible with the previously processed user data.
|
||||
*/
|
||||
static void processUserData(String packageName) {
|
||||
Intent intent = new Intent();
|
||||
intent.setComponent(new ComponentName(packageName,
|
||||
"com.android.tests.rollback.testapp.ProcessUserData"));
|
||||
Context context = InstrumentationRegistry.getContext();
|
||||
|
||||
HandlerThread handlerThread = new HandlerThread("RollbackTestHandlerThread");
|
||||
handlerThread.start();
|
||||
|
||||
// It can sometimes take a while after rollback before the app will
|
||||
// receive this broadcast, so try a few times in a loop.
|
||||
String result = NO_RESPONSE;
|
||||
for (int i = 0; result.equals(NO_RESPONSE) && i < 5; ++i) {
|
||||
BlockingQueue<String> resultQueue = new LinkedBlockingQueue<>();
|
||||
context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (getResultCode() == 1) {
|
||||
resultQueue.add("OK");
|
||||
} else {
|
||||
// If the test app doesn't receive the broadcast or
|
||||
// fails to set the result data, then getResultData
|
||||
// here returns the initial NO_RESPONSE data passed to
|
||||
// the sendOrderedBroadcast call.
|
||||
resultQueue.add(getResultData());
|
||||
}
|
||||
}
|
||||
}, new Handler(handlerThread.getLooper()), 0, NO_RESPONSE, null);
|
||||
|
||||
try {
|
||||
result = resultQueue.take();
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
handlerThread.quit();
|
||||
if (!"OK".equals(result)) {
|
||||
fail(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the rollback info for a recently committed rollback, by matching the rollback id, or
|
||||
* return null if no matching rollback is found.
|
||||
*/
|
||||
static RollbackInfo getRecentlyCommittedRollbackInfoById(int getRollbackId) {
|
||||
for (RollbackInfo info : getRollbackManager().getRecentlyCommittedRollbacks()) {
|
||||
if (info.getRollbackId() == getRollbackId) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send broadcast to crash {@code packageName} {@code count} times. If {@code count} is at least
|
||||
* {@link PackageWatchdog#TRIGGER_FAILURE_COUNT}, watchdog crash detection will be triggered.
|
||||
*/
|
||||
static BroadcastReceiver sendCrashBroadcast(Context context, String packageName, int count)
|
||||
throws InterruptedException, IOException {
|
||||
BlockingQueue<Integer> crashQueue = new SynchronousQueue<>();
|
||||
IntentFilter crashCountFilter = new IntentFilter();
|
||||
crashCountFilter.addAction("com.android.tests.rollback.CRASH");
|
||||
crashCountFilter.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
|
||||
BroadcastReceiver crashCountReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
try {
|
||||
// Sleep long enough for packagewatchdog to be notified of crash
|
||||
Thread.sleep(1000);
|
||||
// Kill app and close AppErrorDialog
|
||||
ActivityManager am = context.getSystemService(ActivityManager.class);
|
||||
am.killBackgroundProcesses(packageName);
|
||||
// Allow another package launch
|
||||
crashQueue.put(intent.getIntExtra("count", 0));
|
||||
} catch (InterruptedException e) {
|
||||
fail("Failed to communicate with test app");
|
||||
}
|
||||
}
|
||||
};
|
||||
context.registerReceiver(crashCountReceiver, crashCountFilter);
|
||||
|
||||
do {
|
||||
launchPackage(packageName);
|
||||
} while(crashQueue.take() < count);
|
||||
return crashCountReceiver;
|
||||
}
|
||||
}
|
||||
@@ -16,26 +16,34 @@
|
||||
|
||||
package com.android.tests.rollback;
|
||||
|
||||
import static com.android.tests.rollback.RollbackTestUtils.assertRollbackInfoEquals;
|
||||
import static com.android.tests.rollback.RollbackTestUtils.getUniqueRollbackInfoForPackage;
|
||||
import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat;
|
||||
import static com.android.cts.rollback.lib.RollbackUtils.getUniqueRollbackInfoForPackage;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.VersionedPackage;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.content.rollback.RollbackInfo;
|
||||
import android.content.rollback.RollbackManager;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import com.android.cts.install.lib.Install;
|
||||
import com.android.cts.install.lib.InstallUtils;
|
||||
import com.android.cts.install.lib.LocalIntentSender;
|
||||
import com.android.cts.install.lib.TestApp;
|
||||
import com.android.cts.install.lib.Uninstall;
|
||||
import com.android.cts.rollback.lib.Rollback;
|
||||
import com.android.cts.rollback.lib.RollbackUtils;
|
||||
import com.android.internal.R;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
@@ -54,23 +62,21 @@ import org.junit.runners.JUnit4;
|
||||
@RunWith(JUnit4.class)
|
||||
public class StagedRollbackTest {
|
||||
|
||||
private static final String TAG = "RollbackTest";
|
||||
private static final String TEST_APP_A = "com.android.tests.rollback.testapp.A";
|
||||
private static final String TEST_APP_A_V1 = "RollbackTestAppAv1.apk";
|
||||
private static final String TEST_APP_A_CRASHING_V2 = "RollbackTestAppACrashingV2.apk";
|
||||
private static final String NETWORK_STACK_CONNECTOR_CLASS =
|
||||
"android.net.INetworkStackConnector";
|
||||
|
||||
private static final String MODULE_META_DATA_PACKAGE = getModuleMetadataPackageName();
|
||||
|
||||
/**
|
||||
* Adopts common shell permissions needed for rollback tests.
|
||||
*/
|
||||
@Before
|
||||
public void adoptShellPermissions() {
|
||||
RollbackTestUtils.adoptShellPermissionIdentity(
|
||||
InstallUtils.adoptShellPermissionIdentity(
|
||||
Manifest.permission.INSTALL_PACKAGES,
|
||||
Manifest.permission.DELETE_PACKAGES,
|
||||
Manifest.permission.TEST_MANAGE_ROLLBACKS,
|
||||
Manifest.permission.KILL_BACKGROUND_PROCESSES);
|
||||
Manifest.permission.FORCE_STOP_PACKAGES);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,7 +84,7 @@ public class StagedRollbackTest {
|
||||
*/
|
||||
@After
|
||||
public void dropShellPermissions() {
|
||||
RollbackTestUtils.dropShellPermissionIdentity();
|
||||
InstallUtils.dropShellPermissionIdentity();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,14 +93,14 @@ public class StagedRollbackTest {
|
||||
*/
|
||||
@Test
|
||||
public void testBadApkOnlyEnableRollback() throws Exception {
|
||||
RollbackTestUtils.uninstall(TEST_APP_A);
|
||||
assertEquals(-1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
|
||||
Uninstall.packages(TestApp.A);
|
||||
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
|
||||
|
||||
RollbackTestUtils.install(TEST_APP_A_V1, false);
|
||||
assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
|
||||
RollbackTestUtils.processUserData(TEST_APP_A);
|
||||
Install.single(TestApp.A1).commit();
|
||||
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
|
||||
InstallUtils.processUserData(TestApp.A);
|
||||
|
||||
RollbackTestUtils.installStaged(true, TEST_APP_A_CRASHING_V2);
|
||||
Install.single(TestApp.ACrashing2).setEnableRollback().setStaged().commit();
|
||||
|
||||
// At this point, the host test driver will reboot the device and run
|
||||
// testBadApkOnlyConfirmEnableRollback().
|
||||
@@ -106,14 +112,16 @@ public class StagedRollbackTest {
|
||||
*/
|
||||
@Test
|
||||
public void testBadApkOnlyConfirmEnableRollback() throws Exception {
|
||||
assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
|
||||
RollbackTestUtils.processUserData(TEST_APP_A);
|
||||
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
|
||||
InstallUtils.processUserData(TestApp.A);
|
||||
|
||||
RollbackManager rm = RollbackTestUtils.getRollbackManager();
|
||||
RollbackManager rm = RollbackUtils.getRollbackManager();
|
||||
RollbackInfo rollback = getUniqueRollbackInfoForPackage(
|
||||
rm.getAvailableRollbacks(), TEST_APP_A);
|
||||
assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
|
||||
assertTrue(rollback.isStaged());
|
||||
rm.getAvailableRollbacks(), TestApp.A);
|
||||
assertThat(rollback).isNotNull();
|
||||
assertThat(rollback).packagesContainsExactly(
|
||||
Rollback.from(TestApp.A2).to(TestApp.A1));
|
||||
assertThat(rollback.isStaged()).isTrue();
|
||||
|
||||
// At this point, the host test driver will run
|
||||
// testBadApkOnlyTriggerRollback().
|
||||
@@ -126,18 +134,8 @@ public class StagedRollbackTest {
|
||||
*/
|
||||
@Test
|
||||
public void testBadApkOnlyTriggerRollback() throws Exception {
|
||||
BroadcastReceiver crashCountReceiver = null;
|
||||
Context context = InstrumentationRegistry.getContext();
|
||||
RollbackManager rm = RollbackTestUtils.getRollbackManager();
|
||||
|
||||
try {
|
||||
// Crash TEST_APP_A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback
|
||||
crashCountReceiver = RollbackTestUtils.sendCrashBroadcast(context, TEST_APP_A, 5);
|
||||
} finally {
|
||||
if (crashCountReceiver != null) {
|
||||
context.unregisterReceiver(crashCountReceiver);
|
||||
}
|
||||
}
|
||||
// Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback
|
||||
RollbackUtils.sendCrashBroadcast(TestApp.A, 5);
|
||||
|
||||
// We expect the device to be rebooted automatically. Wait for that to
|
||||
// happen. At that point, the host test driver will wait for the
|
||||
@@ -153,48 +151,80 @@ public class StagedRollbackTest {
|
||||
*/
|
||||
@Test
|
||||
public void testBadApkOnlyConfirmRollback() throws Exception {
|
||||
assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
|
||||
RollbackTestUtils.processUserData(TEST_APP_A);
|
||||
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
|
||||
InstallUtils.processUserData(TestApp.A);
|
||||
|
||||
RollbackManager rm = RollbackTestUtils.getRollbackManager();
|
||||
RollbackManager rm = RollbackUtils.getRollbackManager();
|
||||
RollbackInfo rollback = getUniqueRollbackInfoForPackage(
|
||||
rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
|
||||
assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback, new VersionedPackage(TEST_APP_A, 2));
|
||||
assertTrue(rollback.isStaged());
|
||||
assertNotEquals(-1, rollback.getCommittedSessionId());
|
||||
rm.getRecentlyCommittedRollbacks(), TestApp.A);
|
||||
assertThat(rollback).isNotNull();
|
||||
assertThat(rollback).packagesContainsExactly(
|
||||
Rollback.from(TestApp.A2).to(TestApp.A1));
|
||||
assertThat(rollback).causePackagesContainsExactly(TestApp.ACrashing2);
|
||||
assertThat(rollback).isStaged();
|
||||
assertThat(rollback.getCommittedSessionId()).isNotEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resetNetworkStack() throws Exception {
|
||||
RollbackManager rm = RollbackTestUtils.getRollbackManager();
|
||||
RollbackManager rm = RollbackUtils.getRollbackManager();
|
||||
String networkStack = getNetworkStackPackageName();
|
||||
|
||||
rm.expireRollbackForPackage(networkStack);
|
||||
RollbackTestUtils.uninstall(networkStack);
|
||||
Uninstall.packages(networkStack);
|
||||
|
||||
assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
|
||||
networkStack));
|
||||
assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
|
||||
networkStack)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void installModuleMetadataPackage() throws Exception {
|
||||
resetModuleMetadataPackage();
|
||||
Context context = InstrumentationRegistry.getContext();
|
||||
PackageInfo metadataPackageInfo = context.getPackageManager().getPackageInfo(
|
||||
MODULE_META_DATA_PACKAGE, 0);
|
||||
String metadataApkPath = metadataPackageInfo.applicationInfo.sourceDir;
|
||||
assertThat(metadataApkPath).isNotNull();
|
||||
assertThat(metadataApkPath).isNotEqualTo("");
|
||||
|
||||
runShellCommand("pm install "
|
||||
+ "-r --enable-rollback --staged --wait "
|
||||
+ metadataApkPath);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertNetworkStackRollbackAvailable() throws Exception {
|
||||
RollbackManager rm = RollbackTestUtils.getRollbackManager();
|
||||
assertNotNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
|
||||
getNetworkStackPackageName()));
|
||||
RollbackManager rm = RollbackUtils.getRollbackManager();
|
||||
assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
|
||||
getNetworkStackPackageName())).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertNetworkStackRollbackCommitted() throws Exception {
|
||||
RollbackManager rm = RollbackTestUtils.getRollbackManager();
|
||||
assertNotNull(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
|
||||
getNetworkStackPackageName()));
|
||||
RollbackManager rm = RollbackUtils.getRollbackManager();
|
||||
assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
|
||||
getNetworkStackPackageName())).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertNoNetworkStackRollbackCommitted() throws Exception {
|
||||
RollbackManager rm = RollbackTestUtils.getRollbackManager();
|
||||
assertNull(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
|
||||
getNetworkStackPackageName()));
|
||||
RollbackManager rm = RollbackUtils.getRollbackManager();
|
||||
assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
|
||||
getNetworkStackPackageName())).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertModuleMetadataRollbackAvailable() throws Exception {
|
||||
RollbackManager rm = RollbackUtils.getRollbackManager();
|
||||
assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
|
||||
MODULE_META_DATA_PACKAGE)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertModuleMetadataRollbackCommitted() throws Exception {
|
||||
RollbackManager rm = RollbackUtils.getRollbackManager();
|
||||
assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
|
||||
MODULE_META_DATA_PACKAGE)).isNotNull();
|
||||
}
|
||||
|
||||
private String getNetworkStackPackageName() {
|
||||
@@ -203,4 +233,65 @@ public class StagedRollbackTest {
|
||||
InstrumentationRegistry.getContext().getPackageManager(), 0);
|
||||
return comp.getPackageName();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPreviouslyAbandonedRollbacksEnableRollback() throws Exception {
|
||||
Uninstall.packages(TestApp.A);
|
||||
Install.single(TestApp.A1).commit();
|
||||
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
|
||||
|
||||
int sessionId = Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
|
||||
PackageInstaller pi = InstrumentationRegistry.getContext().getPackageManager()
|
||||
.getPackageInstaller();
|
||||
pi.abandonSession(sessionId);
|
||||
|
||||
// Remove the first intent sender result, so that the next staged install session does not
|
||||
// erroneously think that it has itself been abandoned.
|
||||
// TODO(b/136260017): Restructure LocalIntentSender to negate the need for this step.
|
||||
LocalIntentSender.getIntentSenderResult();
|
||||
Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPreviouslyAbandonedRollbacksCommitRollback() throws Exception {
|
||||
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
|
||||
InstallUtils.processUserData(TestApp.A);
|
||||
|
||||
RollbackManager rm = RollbackUtils.getRollbackManager();
|
||||
RollbackInfo rollback = getUniqueRollbackInfoForPackage(
|
||||
rm.getAvailableRollbacks(), TestApp.A);
|
||||
RollbackUtils.rollback(rollback.getRollbackId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPreviouslyAbandonedRollbacksCheckUserdataRollback() throws Exception {
|
||||
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
|
||||
InstallUtils.processUserData(TestApp.A);
|
||||
Uninstall.packages(TestApp.A);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getModuleMetadataPackageName() {
|
||||
String packageName = InstrumentationRegistry.getContext().getResources().getString(
|
||||
R.string.config_defaultModuleMetadataProvider);
|
||||
if (TextUtils.isEmpty(packageName)) {
|
||||
return null;
|
||||
}
|
||||
return packageName;
|
||||
}
|
||||
|
||||
private void resetModuleMetadataPackage() {
|
||||
RollbackManager rm = RollbackUtils.getRollbackManager();
|
||||
|
||||
assertThat(MODULE_META_DATA_PACKAGE).isNotNull();
|
||||
rm.expireRollbackForPackage(MODULE_META_DATA_PACKAGE);
|
||||
|
||||
assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
|
||||
MODULE_META_DATA_PACKAGE)).isNull();
|
||||
}
|
||||
|
||||
private void runShellCommand(String cmd) {
|
||||
InstrumentationRegistry.getInstrumentation().getUiAutomation()
|
||||
.executeShellCommand(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,15 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Runs the staged rollback tests.
|
||||
*/
|
||||
@RunWith(DeviceJUnit4ClassRunner.class)
|
||||
public class StagedRollbackTest extends BaseHostJUnit4Test {
|
||||
private static final int NATIVE_CRASHES_THRESHOLD = 5;
|
||||
|
||||
/**
|
||||
* Runs the given phase of a test by calling into the device.
|
||||
* Throws an exception if the test phase fails.
|
||||
@@ -84,6 +88,35 @@ public class StagedRollbackTest extends BaseHostJUnit4Test {
|
||||
runPhase("testBadApkOnlyConfirmRollback");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNativeWatchdogTriggersRollback() throws Exception {
|
||||
//Stage install ModuleMetadata package - this simulates a Mainline module update
|
||||
runPhase("installModuleMetadataPackage");
|
||||
|
||||
// Reboot device to activate staged package
|
||||
getDevice().reboot();
|
||||
getDevice().waitForDeviceAvailable();
|
||||
|
||||
runPhase("assertModuleMetadataRollbackAvailable");
|
||||
|
||||
// crash system_server enough times to trigger a rollback
|
||||
crashProcess("system_server", NATIVE_CRASHES_THRESHOLD);
|
||||
|
||||
// Rollback should be committed automatically now.
|
||||
// Give time for rollback to be committed. This could take a while,
|
||||
// because we need all of the following to happen:
|
||||
// 1. system_server comes back up and boot completes.
|
||||
// 2. Rollback health observer detects updatable crashing signal.
|
||||
// 3. Staged rollback session becomes ready.
|
||||
// 4. Device actually reboots.
|
||||
// So we give a generous timeout here.
|
||||
assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5)));
|
||||
getDevice().waitForDeviceAvailable();
|
||||
|
||||
// verify rollback committed
|
||||
runPhase("assertModuleMetadataRollbackCommitted");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests failed network health check triggers watchdog staged rollbacks.
|
||||
*/
|
||||
@@ -167,6 +200,32 @@ public class StagedRollbackTest extends BaseHostJUnit4Test {
|
||||
runPhase("assertNoNetworkStackRollbackCommitted");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests rolling back user data where there are multiple rollbacks for that package.
|
||||
*/
|
||||
@Test
|
||||
public void testPreviouslyAbandonedRollbacks() throws Exception {
|
||||
runPhase("testPreviouslyAbandonedRollbacksEnableRollback");
|
||||
getDevice().reboot();
|
||||
runPhase("testPreviouslyAbandonedRollbacksCommitRollback");
|
||||
getDevice().reboot();
|
||||
runPhase("testPreviouslyAbandonedRollbacksCheckUserdataRollback");
|
||||
}
|
||||
|
||||
private void crashProcess(String processName, int numberOfCrashes) throws Exception {
|
||||
String pid = "";
|
||||
String lastPid = "invalid";
|
||||
for (int i = 0; i < numberOfCrashes; ++i) {
|
||||
// This condition makes sure before we kill the process, the process is running AND
|
||||
// the last crash was finished.
|
||||
while ("".equals(pid) || lastPid.equals(pid)) {
|
||||
pid = getDevice().executeShellCommand("pidof " + processName);
|
||||
}
|
||||
getDevice().executeShellCommand("kill " + pid);
|
||||
lastPid = pid;
|
||||
}
|
||||
}
|
||||
|
||||
private String getNetworkStackPath() throws DeviceNotAvailableException {
|
||||
// Find the NetworkStack path (can be NetworkStack.apk or NetworkStackNext.apk)
|
||||
return getDevice().executeShellCommand("ls /system/priv-app/NetworkStack*/*.apk");
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
},
|
||||
{
|
||||
"name": "StagedRollbackTest"
|
||||
},
|
||||
{
|
||||
"name": "MultiUserRollbackTest"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2019 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.tests.rollback.testapp.A"
|
||||
android:versionCode="2"
|
||||
android:versionName="2.0" >
|
||||
|
||||
|
||||
<uses-sdk android:minSdkVersion="19" />
|
||||
|
||||
<application android:label="Rollback Test App A v2">
|
||||
<receiver android:name="com.android.tests.rollback.testapp.ProcessUserData"
|
||||
android:exported="true" />
|
||||
<activity android:name="com.android.tests.rollback.testapp.CrashingMainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -1,35 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2018 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.tests.rollback.testapp.A"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0" >
|
||||
|
||||
|
||||
<uses-sdk android:minSdkVersion="19" />
|
||||
|
||||
<application android:label="Rollback Test App A v1">
|
||||
<receiver android:name="com.android.tests.rollback.testapp.ProcessUserData"
|
||||
android:exported="true" />
|
||||
<activity android:name="com.android.tests.rollback.testapp.MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -1,35 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2018 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.tests.rollback.testapp.A"
|
||||
android:versionCode="2"
|
||||
android:versionName="2.0" >
|
||||
|
||||
|
||||
<uses-sdk android:minSdkVersion="19" />
|
||||
|
||||
<application android:label="Rollback Test App A v2">
|
||||
<receiver android:name="com.android.tests.rollback.testapp.ProcessUserData"
|
||||
android:exported="true" />
|
||||
<activity android:name="com.android.tests.rollback.testapp.MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -1,35 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2018 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.tests.rollback.testapp.A"
|
||||
android:versionCode="3"
|
||||
android:versionName="3.0" >
|
||||
|
||||
|
||||
<uses-sdk android:minSdkVersion="19" />
|
||||
|
||||
<application android:label="Rollback Test App A v3">
|
||||
<receiver android:name="com.android.tests.rollback.testapp.ProcessUserData"
|
||||
android:exported="true" />
|
||||
<activity android:name="com.android.tests.rollback.testapp.MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -1,35 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2018 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.tests.rollback.testapp.B"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0" >
|
||||
|
||||
|
||||
<uses-sdk android:minSdkVersion="19" />
|
||||
|
||||
<application android:label="Rollback Test App B v1">
|
||||
<receiver android:name="com.android.tests.rollback.testapp.ProcessUserData"
|
||||
android:exported="true" />
|
||||
<activity android:name="com.android.tests.rollback.testapp.MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -1,35 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2018 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.tests.rollback.testapp.B"
|
||||
android:versionCode="2"
|
||||
android:versionName="2.0" >
|
||||
|
||||
|
||||
<uses-sdk android:minSdkVersion="19" />
|
||||
|
||||
<application android:label="Rollback Test App B v2">
|
||||
<receiver android:name="com.android.tests.rollback.testapp.ProcessUserData"
|
||||
android:exported="true" />
|
||||
<activity android:name="com.android.tests.rollback.testapp.MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2019 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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<integer name="split_version">1</integer>
|
||||
</resources>
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2019 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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<integer name="app_version">1</integer>
|
||||
<integer name="split_version">0</integer>
|
||||
</resources>
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2019 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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<integer name="split_version">2</integer>
|
||||
</resources>
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2019 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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<integer name="split_version">3</integer>
|
||||
</resources>
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2019 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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<integer name="app_version">3</integer>
|
||||
<integer name="split_version">0</integer>
|
||||
</resources>
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.tests.rollback.testapp;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* A crashing test app for testing apk rollback support.
|
||||
*/
|
||||
public class CrashingMainActivity extends Activity {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
incrementCountAndBroadcast();
|
||||
throw new RuntimeException("Intended force crash");
|
||||
}
|
||||
|
||||
private void incrementCountAndBroadcast() {
|
||||
SharedPreferences preferences = getSharedPreferences("prefs", Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
int count = preferences.getInt("crash_count", 0);
|
||||
editor.putInt("crash_count", ++count).commit();
|
||||
|
||||
Intent intent = new Intent("com.android.tests.rollback.CRASH");
|
||||
intent.putExtra("count", count);
|
||||
sendBroadcast(intent);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.tests.rollback.testapp;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* A test app for testing apk rollback support.
|
||||
*/
|
||||
public class MainActivity extends Activity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
try {
|
||||
new ProcessUserData().processUserData(this);
|
||||
} catch (ProcessUserData.UserDataException e) {
|
||||
throw new AssertionError("Failed to process app user data", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.tests.rollback.testapp;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Scanner;
|
||||
|
||||
/**
|
||||
* A broadcast reciever to check for and update user app data version
|
||||
* compatibility.
|
||||
*/
|
||||
public class ProcessUserData extends BroadcastReceiver {
|
||||
|
||||
private static final String TAG = "RollbackTestApp";
|
||||
|
||||
/**
|
||||
* Exception thrown in case of issue with user data.
|
||||
*/
|
||||
public static class UserDataException extends Exception {
|
||||
public UserDataException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public UserDataException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
try {
|
||||
processUserData(context);
|
||||
setResultCode(1);
|
||||
} catch (UserDataException e) {
|
||||
setResultCode(0);
|
||||
setResultData(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the app's user data version to match the app version.
|
||||
*
|
||||
* @param context The application context.
|
||||
* @throws UserDataException in case of problems with app user data.
|
||||
*/
|
||||
public void processUserData(Context context) throws UserDataException {
|
||||
Resources res = context.getResources();
|
||||
String packageName = context.getPackageName();
|
||||
|
||||
int appVersionId = res.getIdentifier("app_version", "integer", packageName);
|
||||
int appVersion = res.getInteger(appVersionId);
|
||||
|
||||
int splitVersionId = res.getIdentifier("split_version", "integer", packageName);
|
||||
int splitVersion = res.getInteger(splitVersionId);
|
||||
|
||||
// Make sure the app version and split versions are compatible.
|
||||
if (appVersion != splitVersion) {
|
||||
throw new UserDataException("Split version " + splitVersion
|
||||
+ " does not match app version " + appVersion);
|
||||
}
|
||||
|
||||
// Read the version of the app's user data and ensure it is compatible
|
||||
// with our version of the application.
|
||||
File versionFile = new File(context.getFilesDir(), "version.txt");
|
||||
try {
|
||||
Scanner s = new Scanner(versionFile);
|
||||
int userDataVersion = s.nextInt();
|
||||
s.close();
|
||||
|
||||
if (userDataVersion > appVersion) {
|
||||
throw new UserDataException("User data is from version " + userDataVersion
|
||||
+ ", which is not compatible with this version " + appVersion
|
||||
+ " of the RollbackTestApp");
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// No problem. This is a fresh install of the app or the user data
|
||||
// has been wiped.
|
||||
}
|
||||
|
||||
// Record the current version of the app in the user data.
|
||||
try {
|
||||
PrintWriter pw = new PrintWriter(versionFile);
|
||||
pw.println(appVersion);
|
||||
pw.close();
|
||||
} catch (IOException e) {
|
||||
throw new UserDataException("Unable to write user data.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user