Rollback user data of apks-in-apex while rolling back the apex
Currently, the RollbackManager is not aware of the apk-in-apex being installed since the install is done by PM during scan phase of boot. As such, RM does not backup the user data of the apk-in-apex. In the new implementation, we ask the RM to snapshot/restore user data of apk-in-apex while resuming the apex session in StagingManager. Bug: 142712057 Test: atest StagedRollbackTest#testRollbackApexWithApk Test: atest AppDataRollbackHelperTest Test: atest RollbackStoreTest Test: atest RollbackUnitTest Change-Id: Ibbaa5d0c98cb883588c085d77bc89c3e8217d76a
This commit is contained in:
@@ -76,6 +76,11 @@ public final class PackageRollbackInfo implements Parcelable {
|
||||
*/
|
||||
private final boolean mIsApex;
|
||||
|
||||
/**
|
||||
* Whether this instance represents the PackageRollbackInfo for an APK in APEX.
|
||||
*/
|
||||
private final boolean mIsApkInApex;
|
||||
|
||||
/*
|
||||
* The list of users for which snapshots have been saved.
|
||||
*/
|
||||
@@ -157,6 +162,10 @@ public final class PackageRollbackInfo implements Parcelable {
|
||||
public @PackageManager.RollbackDataPolicy int getRollbackDataPolicy() {
|
||||
return mRollbackDataPolicy;
|
||||
}
|
||||
/** @hide */
|
||||
public boolean isApkInApex() {
|
||||
return mIsApkInApex;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public IntArray getSnapshottedUsers() {
|
||||
@@ -190,17 +199,18 @@ public final class PackageRollbackInfo implements Parcelable {
|
||||
public PackageRollbackInfo(VersionedPackage packageRolledBackFrom,
|
||||
VersionedPackage packageRolledBackTo,
|
||||
@NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores,
|
||||
boolean isApex, @NonNull IntArray snapshottedUsers,
|
||||
boolean isApex, boolean isApkInApex, @NonNull IntArray snapshottedUsers,
|
||||
@NonNull SparseLongArray ceSnapshotInodes) {
|
||||
this(packageRolledBackFrom, packageRolledBackTo, pendingBackups, pendingRestores, isApex,
|
||||
snapshottedUsers, ceSnapshotInodes, PackageManager.RollbackDataPolicy.RESTORE);
|
||||
isApkInApex, snapshottedUsers, ceSnapshotInodes,
|
||||
PackageManager.RollbackDataPolicy.RESTORE);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public PackageRollbackInfo(VersionedPackage packageRolledBackFrom,
|
||||
VersionedPackage packageRolledBackTo,
|
||||
@NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores,
|
||||
boolean isApex, @NonNull IntArray snapshottedUsers,
|
||||
boolean isApex, boolean isApkInApex, @NonNull IntArray snapshottedUsers,
|
||||
@NonNull SparseLongArray ceSnapshotInodes,
|
||||
@PackageManager.RollbackDataPolicy int rollbackDataPolicy) {
|
||||
this.mVersionRolledBackFrom = packageRolledBackFrom;
|
||||
@@ -209,6 +219,7 @@ public final class PackageRollbackInfo implements Parcelable {
|
||||
this.mPendingRestores = pendingRestores;
|
||||
this.mIsApex = isApex;
|
||||
this.mRollbackDataPolicy = rollbackDataPolicy;
|
||||
this.mIsApkInApex = isApkInApex;
|
||||
this.mSnapshottedUsers = snapshottedUsers;
|
||||
this.mCeSnapshotInodes = ceSnapshotInodes;
|
||||
}
|
||||
@@ -217,6 +228,7 @@ public final class PackageRollbackInfo implements Parcelable {
|
||||
this.mVersionRolledBackFrom = VersionedPackage.CREATOR.createFromParcel(in);
|
||||
this.mVersionRolledBackTo = VersionedPackage.CREATOR.createFromParcel(in);
|
||||
this.mIsApex = in.readBoolean();
|
||||
this.mIsApkInApex = in.readBoolean();
|
||||
this.mPendingRestores = null;
|
||||
this.mPendingBackups = null;
|
||||
this.mSnapshottedUsers = null;
|
||||
@@ -234,6 +246,7 @@ public final class PackageRollbackInfo implements Parcelable {
|
||||
mVersionRolledBackFrom.writeToParcel(out, flags);
|
||||
mVersionRolledBackTo.writeToParcel(out, flags);
|
||||
out.writeBoolean(mIsApex);
|
||||
out.writeBoolean(mIsApkInApex);
|
||||
}
|
||||
|
||||
public static final @NonNull Parcelable.Creator<PackageRollbackInfo> CREATOR =
|
||||
|
||||
@@ -810,6 +810,12 @@ public abstract class PackageManagerInternal {
|
||||
*/
|
||||
public abstract boolean isApexPackage(String packageName);
|
||||
|
||||
/**
|
||||
* Returns list of {@code packageName} of apks inside the given apex.
|
||||
* @param apexPackageName Package name of the apk container of apex
|
||||
*/
|
||||
public abstract List<String> getApksInApex(String apexPackageName);
|
||||
|
||||
/**
|
||||
* Uninstalls given {@code packageName}.
|
||||
*
|
||||
|
||||
@@ -32,10 +32,13 @@ import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageParser;
|
||||
import android.content.pm.parsing.AndroidPackage;
|
||||
import android.os.Environment;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.sysprop.ApexProperties;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Singleton;
|
||||
import android.util.Slog;
|
||||
|
||||
@@ -44,15 +47,20 @@ import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.os.BackgroundThread;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
|
||||
import com.google.android.collect.Lists;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -97,12 +105,27 @@ abstract class ApexManager {
|
||||
* Minimal information about APEX mount points and the original APEX package they refer to.
|
||||
*/
|
||||
static class ActiveApexInfo {
|
||||
@Nullable public final String apexModuleName;
|
||||
public final File apexDirectory;
|
||||
public final File preinstalledApexPath;
|
||||
public final File preInstalledApexPath;
|
||||
|
||||
private ActiveApexInfo(File apexDirectory, File preinstalledApexPath) {
|
||||
private ActiveApexInfo(File apexDirectory, File preInstalledApexPath) {
|
||||
this(null, apexDirectory, preInstalledApexPath);
|
||||
}
|
||||
|
||||
private ActiveApexInfo(@Nullable String apexModuleName, File apexDirectory,
|
||||
File preInstalledApexPath) {
|
||||
this.apexModuleName = apexModuleName;
|
||||
this.apexDirectory = apexDirectory;
|
||||
this.preinstalledApexPath = preinstalledApexPath;
|
||||
this.preInstalledApexPath = preInstalledApexPath;
|
||||
}
|
||||
|
||||
private ActiveApexInfo(ApexInfo apexInfo) {
|
||||
this(
|
||||
apexInfo.moduleName,
|
||||
new File(Environment.getApexDirectory() + File.separator
|
||||
+ apexInfo.moduleName),
|
||||
new File(apexInfo.preinstalledModulePath));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,6 +254,17 @@ abstract class ApexManager {
|
||||
*/
|
||||
abstract boolean uninstallApex(String apexPackagePath);
|
||||
|
||||
/**
|
||||
* Registers an APK package as an embedded apk of apex.
|
||||
*/
|
||||
abstract void registerApkInApex(AndroidPackage pkg);
|
||||
|
||||
/**
|
||||
* Returns list of {@code packageName} of apks inside the given apex.
|
||||
* @param apexPackageName Package name of the apk container of apex
|
||||
*/
|
||||
abstract List<String> getApksInApex(String apexPackageName);
|
||||
|
||||
/**
|
||||
* Dumps various state information to the provided {@link PrintWriter} object.
|
||||
*
|
||||
@@ -255,16 +289,33 @@ abstract class ApexManager {
|
||||
static class ApexManagerImpl extends ApexManager {
|
||||
private final IApexService mApexService;
|
||||
private final Object mLock = new Object();
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private Set<ActiveApexInfo> mActiveApexInfosCache;
|
||||
|
||||
/**
|
||||
* A map from {@code APEX packageName} to the {@Link PackageInfo} generated from the {@code
|
||||
* AndroidManifest.xml}
|
||||
*
|
||||
* <p>Note that key of this map is {@code packageName} field of the corresponding {@code
|
||||
* AndroidManifest.xml}.
|
||||
*/
|
||||
* Contains the list of {@code packageName}s of apks-in-apex for given
|
||||
* {@code apexModuleName}. See {@link #mPackageNameToApexModuleName} to understand the
|
||||
* difference between {@code packageName} and {@code apexModuleName}.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private Map<String, List<String>> mApksInApex = new ArrayMap<>();
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private List<PackageInfo> mAllPackagesCache;
|
||||
|
||||
/**
|
||||
* An APEX is a file format that delivers the apex-payload wrapped in an apk container. The
|
||||
* apk container has a reference name, called {@code packageName}, which is found inside the
|
||||
* {@code AndroidManifest.xml}. The apex payload inside the container also has a reference
|
||||
* name, called {@code apexModuleName}, which is found in {@code apex_manifest.json} file.
|
||||
*
|
||||
* {@link #mPackageNameToApexModuleName} contains the mapping from {@code packageName} of
|
||||
* the apk container to {@code apexModuleName} of the apex-payload inside.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private Map<String, String> mPackageNameToApexModuleName;
|
||||
|
||||
ApexManagerImpl(IApexService apexService) {
|
||||
mApexService = apexService;
|
||||
}
|
||||
@@ -291,18 +342,25 @@ abstract class ApexManager {
|
||||
|
||||
@Override
|
||||
List<ActiveApexInfo> getActiveApexInfos() {
|
||||
try {
|
||||
return Arrays.stream(mApexService.getActivePackages())
|
||||
.map(apexInfo -> new ActiveApexInfo(
|
||||
new File(
|
||||
Environment.getApexDirectory() + File.separator
|
||||
+ apexInfo.moduleName),
|
||||
new File(apexInfo.preinstalledModulePath))).collect(
|
||||
Collectors.toList());
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Unable to retrieve packages from apexservice", e);
|
||||
synchronized (mLock) {
|
||||
if (mActiveApexInfosCache == null) {
|
||||
try {
|
||||
mActiveApexInfosCache = new ArraySet<>();
|
||||
final ApexInfo[] activePackages = mApexService.getActivePackages();
|
||||
for (int i = 0; i < activePackages.length; i++) {
|
||||
ApexInfo apexInfo = activePackages[i];
|
||||
mActiveApexInfosCache.add(new ActiveApexInfo(apexInfo));
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Unable to retrieve packages from apexservice", e);
|
||||
}
|
||||
}
|
||||
if (mActiveApexInfosCache != null) {
|
||||
return new ArrayList<>(mActiveApexInfosCache);
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -325,6 +383,7 @@ abstract class ApexManager {
|
||||
}
|
||||
try {
|
||||
mAllPackagesCache = new ArrayList<>();
|
||||
mPackageNameToApexModuleName = new HashMap<>();
|
||||
HashSet<String> activePackagesSet = new HashSet<>();
|
||||
HashSet<String> factoryPackagesSet = new HashSet<>();
|
||||
final ApexInfo[] allPkgs = mApexService.getAllPackages();
|
||||
@@ -350,6 +409,7 @@ abstract class ApexManager {
|
||||
final PackageInfo packageInfo =
|
||||
PackageParser.generatePackageInfo(pkg, ai, flags);
|
||||
mAllPackagesCache.add(packageInfo);
|
||||
mPackageNameToApexModuleName.put(packageInfo.packageName, ai.moduleName);
|
||||
if (ai.isActive) {
|
||||
if (activePackagesSet.contains(packageInfo.packageName)) {
|
||||
throw new IllegalStateException(
|
||||
@@ -366,7 +426,6 @@ abstract class ApexManager {
|
||||
}
|
||||
factoryPackagesSet.add(packageInfo.packageName);
|
||||
}
|
||||
|
||||
}
|
||||
} catch (RemoteException re) {
|
||||
Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
|
||||
@@ -533,6 +592,37 @@ abstract class ApexManager {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerApkInApex(AndroidPackage pkg) {
|
||||
synchronized (mLock) {
|
||||
final Iterator<ActiveApexInfo> it = mActiveApexInfosCache.iterator();
|
||||
while (it.hasNext()) {
|
||||
final ActiveApexInfo aai = it.next();
|
||||
if (pkg.getBaseCodePath().startsWith(aai.apexDirectory.getAbsolutePath())) {
|
||||
List<String> apks = mApksInApex.get(aai.apexModuleName);
|
||||
if (apks == null) {
|
||||
apks = Lists.newArrayList();
|
||||
mApksInApex.put(aai.apexModuleName, apks);
|
||||
}
|
||||
apks.add(pkg.getPackageName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
List<String> getApksInApex(String apexPackageName) {
|
||||
// TODO(b/142712057): Avoid calling populateAllPackagesCacheIfNeeded during boot.
|
||||
populateAllPackagesCacheIfNeeded();
|
||||
synchronized (mLock) {
|
||||
String moduleName = mPackageNameToApexModuleName.get(apexPackageName);
|
||||
if (moduleName == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return mApksInApex.getOrDefault(moduleName, Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump information about the packages contained in a particular cache
|
||||
* @param packagesCache the cache to print information about.
|
||||
@@ -614,7 +704,6 @@ abstract class ApexManager {
|
||||
* updating APEX packages.
|
||||
*/
|
||||
private static final class ApexManagerFlattenedApex extends ApexManager {
|
||||
|
||||
@Override
|
||||
List<ActiveApexInfo> getActiveApexInfos() {
|
||||
// There is no apexd running in case of flattened apex
|
||||
@@ -720,6 +809,16 @@ abstract class ApexManager {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerApkInApex(AndroidPackage pkg) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
List<String> getApksInApex(String apexPackageName) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
void dump(PrintWriter pw, String packageName) {
|
||||
// No-op
|
||||
|
||||
@@ -188,7 +188,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
|
||||
}
|
||||
};
|
||||
|
||||
public PackageInstallerService(Context context, PackageManagerService pm, ApexManager am) {
|
||||
public PackageInstallerService(Context context, PackageManagerService pm) {
|
||||
mContext = context;
|
||||
mPm = pm;
|
||||
mPermissionManager = LocalServices.getService(PermissionManagerServiceInternal.class);
|
||||
@@ -206,9 +206,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
|
||||
mSessionsDir = new File(Environment.getDataSystemDirectory(), "install_sessions");
|
||||
mSessionsDir.mkdirs();
|
||||
|
||||
mApexManager = am;
|
||||
|
||||
mStagingManager = new StagingManager(this, am, context);
|
||||
mApexManager = ApexManager.getInstance();
|
||||
mStagingManager = new StagingManager(this, context);
|
||||
}
|
||||
|
||||
boolean okToSendBroadcasts() {
|
||||
|
||||
@@ -492,6 +492,7 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
static final int SCAN_AS_PRODUCT = 1 << 20;
|
||||
static final int SCAN_AS_SYSTEM_EXT = 1 << 21;
|
||||
static final int SCAN_AS_ODM = 1 << 22;
|
||||
static final int SCAN_AS_APK_IN_APEX = 1 << 23;
|
||||
|
||||
@IntDef(flag = true, prefix = { "SCAN_" }, value = {
|
||||
SCAN_NO_DEX,
|
||||
@@ -2589,6 +2590,9 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
& (SCAN_AS_VENDOR | SCAN_AS_ODM | SCAN_AS_PRODUCT | SCAN_AS_SYSTEM_EXT)) != 0) {
|
||||
return true;
|
||||
}
|
||||
if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3332,7 +3336,7 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
}
|
||||
}
|
||||
|
||||
mInstallerService = new PackageInstallerService(mContext, this, mApexManager);
|
||||
mInstallerService = new PackageInstallerService(mContext, this);
|
||||
final Pair<ComponentName, String> instantAppResolverComponent =
|
||||
getInstantAppResolverLPr();
|
||||
if (instantAppResolverComponent != null) {
|
||||
@@ -11710,6 +11714,9 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
mSettings.insertPackageSettingLPw(pkgSetting, pkg);
|
||||
// Add the new setting to mPackages
|
||||
mPackages.put(pkg.getAppInfoPackageName(), pkg);
|
||||
if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
|
||||
mApexManager.registerApkInApex(pkg);
|
||||
}
|
||||
|
||||
// Add the package's KeySets to the global KeySetManagerService
|
||||
KeySetManagerService ksms = mSettings.mKeySetManagerService;
|
||||
@@ -17757,10 +17764,10 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
ApexManager.ActiveApexInfo apexInfo) {
|
||||
for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
|
||||
SystemPartition sp = SYSTEM_PARTITIONS.get(i);
|
||||
if (apexInfo.preinstalledApexPath.getAbsolutePath().startsWith(
|
||||
if (apexInfo.preInstalledApexPath.getAbsolutePath().startsWith(
|
||||
sp.folder.getAbsolutePath())) {
|
||||
return new SystemPartition(apexInfo.apexDirectory, sp.scanFlag,
|
||||
false /* hasOverlays */);
|
||||
return new SystemPartition(apexInfo.apexDirectory,
|
||||
sp.scanFlag | SCAN_AS_APK_IN_APEX, false /* hasOverlays */);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -23451,6 +23458,11 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
return PackageManagerService.this.mApexManager.isApexPackage(packageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getApksInApex(String apexPackageName) {
|
||||
return PackageManagerService.this.mApexManager.getApksInApex(apexPackageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uninstallApex(String packageName, long versionCode, int userId,
|
||||
IntentSender intentSender, int flags) {
|
||||
|
||||
@@ -33,11 +33,13 @@ import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.content.pm.PackageInstaller.SessionInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.content.pm.PackageParser;
|
||||
import android.content.pm.PackageParser.PackageParserException;
|
||||
import android.content.pm.PackageParser.SigningDetails;
|
||||
import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.content.pm.parsing.AndroidPackage;
|
||||
import android.content.rollback.IRollbackManager;
|
||||
import android.content.rollback.RollbackInfo;
|
||||
import android.content.rollback.RollbackManager;
|
||||
@@ -50,6 +52,8 @@ import android.os.ParcelFileDescriptor;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManagerInternal;
|
||||
import android.os.storage.IStorageManager;
|
||||
import android.os.storage.StorageManager;
|
||||
import android.util.IntArray;
|
||||
@@ -61,6 +65,7 @@ import android.util.apk.ApkSignatureVerifier;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.content.PackageHelper;
|
||||
import com.android.internal.os.BackgroundThread;
|
||||
import com.android.server.LocalServices;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -93,10 +98,11 @@ public class StagingManager {
|
||||
@GuardedBy("mStagedSessions")
|
||||
private final SparseIntArray mSessionRollbackIds = new SparseIntArray();
|
||||
|
||||
StagingManager(PackageInstallerService pi, ApexManager am, Context context) {
|
||||
StagingManager(PackageInstallerService pi, Context context) {
|
||||
mPi = pi;
|
||||
mApexManager = am;
|
||||
mContext = context;
|
||||
|
||||
mApexManager = ApexManager.getInstance();
|
||||
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||
mPreRebootVerificationHandler = new PreRebootVerificationHandler(
|
||||
BackgroundThread.get().getLooper());
|
||||
@@ -334,6 +340,88 @@ public class StagingManager {
|
||||
return PackageHelper.getStorageManager().needsCheckpoint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apks inside apex are not installed using apk-install flow. They are scanned from the system
|
||||
* directory directly by PackageManager, as such, RollbackManager need to handle their data
|
||||
* separately here.
|
||||
*/
|
||||
private void snapshotAndRestoreApkInApexUserData(PackageInstallerSession session) {
|
||||
// We want to process apks inside apex. So current session needs to contain apex.
|
||||
if (!sessionContainsApex(session)) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean doSnapshotOrRestore =
|
||||
(session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
|
||||
|| session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK;
|
||||
if (!doSnapshotOrRestore) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find all the apex sessions that needs processing
|
||||
List<PackageInstallerSession> apexSessions = new ArrayList<>();
|
||||
if (session.isMultiPackage()) {
|
||||
List<PackageInstallerSession> childrenSessions = new ArrayList<>();
|
||||
synchronized (mStagedSessions) {
|
||||
for (int childSessionId : session.getChildSessionIds()) {
|
||||
PackageInstallerSession childSession = mStagedSessions.get(childSessionId);
|
||||
if (childSession != null) {
|
||||
childrenSessions.add(childSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (PackageInstallerSession childSession : childrenSessions) {
|
||||
if (sessionContainsApex(childSession)) {
|
||||
apexSessions.add(childSession);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
apexSessions.add(session);
|
||||
}
|
||||
|
||||
// For each apex, process the apks inside it
|
||||
for (PackageInstallerSession apexSession : apexSessions) {
|
||||
List<String> apksInApex = mApexManager.getApksInApex(apexSession.getPackageName());
|
||||
for (String apk: apksInApex) {
|
||||
snapshotAndRestoreApkInApexUserData(apk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void snapshotAndRestoreApkInApexUserData(String packageName) {
|
||||
IRollbackManager rm = IRollbackManager.Stub.asInterface(
|
||||
ServiceManager.getService(Context.ROLLBACK_SERVICE));
|
||||
|
||||
PackageManagerInternal mPmi = LocalServices.getService(PackageManagerInternal.class);
|
||||
AndroidPackage pkg = mPmi.getPackage(packageName);
|
||||
if (pkg == null) {
|
||||
Slog.e(TAG, "Could not find package: " + packageName
|
||||
+ "for snapshotting/restoring user data.");
|
||||
return;
|
||||
}
|
||||
final String seInfo = pkg.getSeInfo();
|
||||
final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
|
||||
final int[] allUsers = um.getUserIds();
|
||||
|
||||
int appId = -1;
|
||||
long ceDataInode = -1;
|
||||
final PackageSetting ps = (PackageSetting) mPmi.getPackageSetting(packageName);
|
||||
if (ps != null && rm != null) {
|
||||
appId = ps.appId;
|
||||
ceDataInode = ps.getCeDataInode(UserHandle.USER_SYSTEM);
|
||||
// NOTE: We ignore the user specified in the InstallParam because we know this is
|
||||
// an update, and hence need to restore data for all installed users.
|
||||
final int[] installedUsers = ps.queryInstalledUsers(allUsers, true);
|
||||
|
||||
try {
|
||||
rm.snapshotAndRestoreUserData(packageName, installedUsers, appId, ceDataInode,
|
||||
seInfo, 0 /*token*/);
|
||||
} catch (RemoteException re) {
|
||||
Slog.e(TAG, "Error snapshotting/restoring user data: " + re);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resumeSession(@NonNull PackageInstallerSession session) {
|
||||
Slog.d(TAG, "Resuming session " + session.sessionId);
|
||||
|
||||
@@ -407,6 +495,7 @@ public class StagingManager {
|
||||
abortCheckpoint();
|
||||
return;
|
||||
}
|
||||
snapshotAndRestoreApkInApexUserData(session);
|
||||
Slog.i(TAG, "APEX packages in session " + session.sessionId
|
||||
+ " were successfully activated. Proceeding with APK packages, if any");
|
||||
}
|
||||
@@ -529,7 +618,7 @@ public class StagingManager {
|
||||
Arrays.stream(session.getChildSessionIds())
|
||||
// Retrieve cached sessions matching ids.
|
||||
.mapToObj(i -> mStagedSessions.get(i))
|
||||
// Filter only the ones containing APKs.s
|
||||
// Filter only the ones containing APKs.
|
||||
.filter(childSession -> !isApexSession(childSession))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -323,8 +323,8 @@ class Rollback {
|
||||
new VersionedPackage(packageName, newVersion),
|
||||
new VersionedPackage(packageName, installedVersion),
|
||||
new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */,
|
||||
isApex, new IntArray(), new SparseLongArray() /* ceSnapshotInodes */,
|
||||
rollbackDataPolicy);
|
||||
isApex, false /* isApkInApex */, new IntArray(),
|
||||
new SparseLongArray() /* ceSnapshotInodes */, rollbackDataPolicy);
|
||||
|
||||
synchronized (mLock) {
|
||||
info.getPackages().add(packageRollbackInfo);
|
||||
@@ -333,6 +333,30 @@ class Rollback {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables this rollback for the provided apk-in-apex.
|
||||
*
|
||||
* @return boolean True if the rollback was enabled successfully for the specified package.
|
||||
*/
|
||||
boolean enableForPackageInApex(String packageName, long installedVersion,
|
||||
int rollbackDataPolicy) {
|
||||
// TODO(b/142712057): Extract the new version number of apk-in-apex
|
||||
// The new version for the apk-in-apex is set to 0 for now. If the package is then further
|
||||
// updated via non-staged install flow, then RollbackManagerServiceImpl#onPackageReplaced()
|
||||
// will be called and this rollback will be deleted. Other ways of package update have not
|
||||
// been handled yet.
|
||||
PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(
|
||||
new VersionedPackage(packageName, 0 /* newVersion */),
|
||||
new VersionedPackage(packageName, installedVersion),
|
||||
new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */,
|
||||
false /* isApex */, true /* isApkInApex */, new IntArray(),
|
||||
new SparseLongArray() /* ceSnapshotInodes */, rollbackDataPolicy);
|
||||
synchronized (mLock) {
|
||||
info.getPackages().add(packageRollbackInfo);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Snapshots user data for the provided package and user ids. Does nothing if this rollback is
|
||||
* not in the ENABLING state.
|
||||
@@ -428,6 +452,11 @@ class Rollback {
|
||||
parentSessionId);
|
||||
|
||||
for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
|
||||
if (pkgRollbackInfo.isApkInApex()) {
|
||||
// No need to issue a downgrade install request for apk-in-apex. It will
|
||||
// be rolled back when its parent apex is downgraded.
|
||||
continue;
|
||||
}
|
||||
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
|
||||
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
|
||||
String installerPackageName = mInstallerPackageName;
|
||||
@@ -453,7 +482,8 @@ class Rollback {
|
||||
this, pkgRollbackInfo.getPackageName());
|
||||
if (packageCodePaths == null) {
|
||||
sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
|
||||
"Backup copy of package inaccessible");
|
||||
"Backup copy of package: "
|
||||
+ pkgRollbackInfo.getPackageName() + " is inaccessible");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -891,9 +891,36 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
|
||||
}
|
||||
|
||||
ApplicationInfo appInfo = pkgInfo.applicationInfo;
|
||||
return rollback.enableForPackage(packageName, newPackage.versionCode,
|
||||
boolean success = rollback.enableForPackage(packageName, newPackage.versionCode,
|
||||
pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir,
|
||||
appInfo.splitSourceDirs, session.rollbackDataPolicy);
|
||||
if (!success) {
|
||||
return success;
|
||||
}
|
||||
|
||||
if (isApex) {
|
||||
// Check if this apex contains apks inside it. If true, then they should be added as
|
||||
// a RollbackPackageInfo into this rollback
|
||||
final PackageManagerInternal pmi = LocalServices.getService(
|
||||
PackageManagerInternal.class);
|
||||
List<String> apksInApex = pmi.getApksInApex(packageName);
|
||||
for (String apkInApex : apksInApex) {
|
||||
// Get information about the currently installed package.
|
||||
final PackageInfo apkPkgInfo;
|
||||
try {
|
||||
apkPkgInfo = getPackageInfo(apkInApex);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// TODO: Support rolling back fresh package installs rather than
|
||||
// fail here. Test this case.
|
||||
Slog.e(TAG, apkInApex + " is not installed");
|
||||
return false;
|
||||
}
|
||||
success = rollback.enableForPackageInApex(
|
||||
apkInApex, apkPkgInfo.getLongVersionCode(), session.rollbackDataPolicy);
|
||||
if (!success) return success;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -907,9 +934,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
|
||||
getHandler().post(() -> {
|
||||
snapshotUserDataInternal(packageName, userIds);
|
||||
restoreUserDataInternal(packageName, userIds, appId, seInfo);
|
||||
final PackageManagerInternal pmi = LocalServices.getService(
|
||||
PackageManagerInternal.class);
|
||||
pmi.finishPackageInstall(token, false);
|
||||
// When this method is called as part of the install flow, a positive token number is
|
||||
// passed to it. Need to notify the PackageManager when we are done.
|
||||
if (token > 0) {
|
||||
final PackageManagerInternal pmi = LocalServices.getService(
|
||||
PackageManagerInternal.class);
|
||||
pmi.finishPackageInstall(token, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1195,11 +1226,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (rollback.getPackageCount() != newRollback.getPackageSessionIdCount()) {
|
||||
Slog.e(TAG, "Failed to enable rollback for all packages in session.");
|
||||
rollback.delete(mAppDataRollbackHelper);
|
||||
return null;
|
||||
}
|
||||
// TODO(b/142712057): Re-enable this check. For that we need count of apks-in-apex
|
||||
// if (rollback.getPackageCount() != newRollback.getPackageSessionIdCount()) {
|
||||
// Slog.e(TAG, "Failed to enable rollback for all packages in session.");
|
||||
// rollback.delete(mAppDataRollbackHelper);
|
||||
// return null;
|
||||
// }
|
||||
|
||||
rollback.saveRollback();
|
||||
synchronized (mLock) {
|
||||
|
||||
@@ -341,6 +341,7 @@ class RollbackStore {
|
||||
json.put("pendingRestores", convertToJsonArray(pendingRestores));
|
||||
|
||||
json.put("isApex", info.isApex());
|
||||
json.put("isApkInApex", info.isApkInApex());
|
||||
|
||||
// Field is named 'installedUsers' for legacy reasons.
|
||||
json.put("installedUsers", convertToJsonArray(snapshottedUsers));
|
||||
@@ -364,6 +365,7 @@ class RollbackStore {
|
||||
json.getJSONArray("pendingRestores"));
|
||||
|
||||
final boolean isApex = json.getBoolean("isApex");
|
||||
final boolean isApkInApex = json.getBoolean("isApkInApex");
|
||||
|
||||
// Field is named 'installedUsers' for legacy reasons.
|
||||
final IntArray snapshottedUsers = convertToIntArray(json.getJSONArray("installedUsers"));
|
||||
@@ -375,8 +377,8 @@ class RollbackStore {
|
||||
PackageManager.RollbackDataPolicy.RESTORE);
|
||||
|
||||
return new PackageRollbackInfo(versionRolledBackFrom, versionRolledBackTo,
|
||||
pendingBackups, pendingRestores, isApex, snapshottedUsers, ceSnapshotInodes,
|
||||
rollbackDataPolicy);
|
||||
pendingBackups, pendingRestores, isApex, isApkInApex, snapshottedUsers,
|
||||
ceSnapshotInodes, rollbackDataPolicy);
|
||||
}
|
||||
|
||||
private static JSONArray versionedPackagesToJson(List<VersionedPackage> packages)
|
||||
|
||||
@@ -98,7 +98,7 @@ public class AppDataRollbackHelperTest {
|
||||
final int[] installedUsers) {
|
||||
return new PackageRollbackInfo(
|
||||
new VersionedPackage(packageName, 2), new VersionedPackage(packageName, 1),
|
||||
new IntArray(), new ArrayList<>(), false, IntArray.wrap(installedUsers),
|
||||
new IntArray(), new ArrayList<>(), false, false, IntArray.wrap(installedUsers),
|
||||
new SparseLongArray());
|
||||
}
|
||||
|
||||
|
||||
@@ -87,13 +87,15 @@ public class RollbackStoreTest {
|
||||
+ "[{'versionRolledBackFrom':{'packageName':'blah','longVersionCode':55},"
|
||||
+ "'versionRolledBackTo':{'packageName':'blah1','longVersionCode':50},'pendingBackups':"
|
||||
+ "[59,1245,124544],'pendingRestores':[{'userId':498,'appId':32322,'seInfo':'wombles'},"
|
||||
+ "{'userId':-895,'appId':1,'seInfo':'pingu'}],'isApex':false,'installedUsers':"
|
||||
+ "{'userId':-895,'appId':1,'seInfo':'pingu'}],'isApex':false,'isApkInApex':false,"
|
||||
+ "'installedUsers':"
|
||||
+ "[498468432,1111,98464],'ceSnapshotInodes':[{'userId':1,'ceSnapshotInode':-6},"
|
||||
+ "{'userId':2222,'ceSnapshotInode':81641654445},{'userId':546546,"
|
||||
+ "'ceSnapshotInode':345689375}]},{'versionRolledBackFrom':{'packageName':'chips',"
|
||||
+ "'longVersionCode':28},'versionRolledBackTo':{'packageName':'com.chips.test',"
|
||||
+ "'longVersionCode':48},'pendingBackups':[5],'pendingRestores':[{'userId':18,"
|
||||
+ "'appId':-12,'seInfo':''}],'isApex':false,'installedUsers':[55,79],"
|
||||
+ "'appId':-12,'seInfo':''}],'isApex':false,'isApkInApex':false,"
|
||||
+ "'installedUsers':[55,79],"
|
||||
+ "'ceSnapshotInodes':[]}],'isStaged':false,'causePackages':[{'packageName':'hello',"
|
||||
+ "'longVersionCode':23},{'packageName':'something','longVersionCode':999}],"
|
||||
+ "'committedSessionId':45654465},'timestamp':'2019-10-01T12:29:08.855Z',"
|
||||
@@ -155,7 +157,7 @@ public class RollbackStoreTest {
|
||||
PackageRollbackInfo pkgInfo1 =
|
||||
new PackageRollbackInfo(new VersionedPackage("com.made.up", 18),
|
||||
new VersionedPackage("com.something.else", 5), new IntArray(),
|
||||
new ArrayList<>(), false, new IntArray(), new SparseLongArray());
|
||||
new ArrayList<>(), false, false, new IntArray(), new SparseLongArray());
|
||||
pkgInfo1.getPendingBackups().add(8);
|
||||
pkgInfo1.getPendingBackups().add(888);
|
||||
pkgInfo1.getPendingBackups().add(88885);
|
||||
@@ -175,7 +177,7 @@ public class RollbackStoreTest {
|
||||
PackageRollbackInfo pkgInfo2 = new PackageRollbackInfo(
|
||||
new VersionedPackage("another.package", 2),
|
||||
new VersionedPackage("com.test.ing", 48888), new IntArray(), new ArrayList<>(),
|
||||
false, new IntArray(), new SparseLongArray());
|
||||
false, false, new IntArray(), new SparseLongArray());
|
||||
pkgInfo2.getPendingBackups().add(57);
|
||||
|
||||
pkgInfo2.getPendingRestores().add(
|
||||
@@ -205,7 +207,7 @@ public class RollbackStoreTest {
|
||||
|
||||
PackageRollbackInfo pkgInfo1 = new PackageRollbackInfo(new VersionedPackage("blah", 55),
|
||||
new VersionedPackage("blah1", 50), new IntArray(), new ArrayList<>(),
|
||||
false, new IntArray(), new SparseLongArray());
|
||||
false, false, new IntArray(), new SparseLongArray());
|
||||
pkgInfo1.getPendingBackups().add(59);
|
||||
pkgInfo1.getPendingBackups().add(1245);
|
||||
pkgInfo1.getPendingBackups().add(124544);
|
||||
@@ -224,7 +226,7 @@ public class RollbackStoreTest {
|
||||
|
||||
PackageRollbackInfo pkgInfo2 = new PackageRollbackInfo(new VersionedPackage("chips", 28),
|
||||
new VersionedPackage("com.chips.test", 48), new IntArray(), new ArrayList<>(),
|
||||
false, new IntArray(), new SparseLongArray());
|
||||
false, false, new IntArray(), new SparseLongArray());
|
||||
pkgInfo2.getPendingBackups().add(5);
|
||||
|
||||
pkgInfo2.getPendingRestores().add(
|
||||
|
||||
@@ -295,7 +295,8 @@ public class RollbackUnitTest {
|
||||
String packageName, long fromVersion, long toVersion, boolean isApex) {
|
||||
return new PackageRollbackInfo(new VersionedPackage(packageName, fromVersion),
|
||||
new VersionedPackage(packageName, toVersion),
|
||||
new IntArray(), new ArrayList<>(), isApex, new IntArray(), new SparseLongArray());
|
||||
new IntArray(), new ArrayList<>(), isApex, false, new IntArray(),
|
||||
new SparseLongArray());
|
||||
}
|
||||
|
||||
private static class PackageRollbackInfoForPackage implements
|
||||
|
||||
@@ -19,15 +19,17 @@ android_test {
|
||||
static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"],
|
||||
test_suites: ["general-tests"],
|
||||
test_config: "RollbackTest.xml",
|
||||
java_resources: [":com.android.apex.apkrollback.test_v2"],
|
||||
}
|
||||
|
||||
java_test_host {
|
||||
name: "StagedRollbackTest",
|
||||
srcs: ["StagedRollbackTest/src/**/*.java"],
|
||||
libs: ["tradefed"],
|
||||
static_libs: ["testng"],
|
||||
static_libs: ["testng", "compatibility-tradefed"],
|
||||
test_suites: ["general-tests"],
|
||||
test_config: "StagedRollbackTest.xml",
|
||||
data: [":com.android.apex.apkrollback.test_v1"],
|
||||
}
|
||||
|
||||
java_test_host {
|
||||
@@ -37,3 +39,44 @@ java_test_host {
|
||||
test_suites: ["general-tests"],
|
||||
test_config: "MultiUserRollbackTest.xml",
|
||||
}
|
||||
|
||||
genrule {
|
||||
name: "com.android.apex.apkrollback.test.pem",
|
||||
out: ["com.android.apex.apkrollback.test.pem"],
|
||||
cmd: "openssl genrsa -out $(out) 4096",
|
||||
}
|
||||
|
||||
genrule {
|
||||
name: "com.android.apex.apkrollback.test.pubkey",
|
||||
srcs: [":com.android.apex.apkrollback.test.pem"],
|
||||
out: ["com.android.apex.apkrollback.test.pubkey"],
|
||||
tools: ["avbtool"],
|
||||
cmd: "$(location avbtool) extract_public_key --key $(in) --output $(out)",
|
||||
}
|
||||
|
||||
apex_key {
|
||||
name: "com.android.apex.apkrollback.test.key",
|
||||
private_key: ":com.android.apex.apkrollback.test.pem",
|
||||
public_key: ":com.android.apex.apkrollback.test.pubkey",
|
||||
installable: false,
|
||||
}
|
||||
|
||||
apex {
|
||||
name: "com.android.apex.apkrollback.test_v1",
|
||||
manifest: "testdata/manifest_v1.json",
|
||||
androidManifest: "testdata/AndroidManifest.xml",
|
||||
file_contexts: ":apex.test-file_contexts",
|
||||
key: "com.android.apex.apkrollback.test.key",
|
||||
apps: ["TestAppAv1"],
|
||||
installable: false,
|
||||
}
|
||||
|
||||
apex {
|
||||
name: "com.android.apex.apkrollback.test_v2",
|
||||
manifest: "testdata/manifest_v2.json",
|
||||
androidManifest: "testdata/AndroidManifest.xml",
|
||||
file_contexts: ":apex.test-file_contexts",
|
||||
key: "com.android.apex.apkrollback.test.key",
|
||||
apps: ["TestAppAv2"],
|
||||
installable: false,
|
||||
}
|
||||
@@ -435,11 +435,64 @@ public class StagedRollbackTest {
|
||||
// testNativeWatchdogTriggersRollback will fail if multiple staged sessions are
|
||||
// committed on a device which doesn't support checkpoint. Let's clean up all rollbacks
|
||||
// so there is only one rollback to commit when testing native crashes.
|
||||
RollbackManager rm = RollbackUtils.getRollbackManager();
|
||||
RollbackManager rm = RollbackUtils.getRollbackManager();
|
||||
rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream())
|
||||
.map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage);
|
||||
}
|
||||
|
||||
private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
|
||||
private static final TestApp TEST_APEX_WITH_APK_V1 = new TestApp("TestApexWithApkV1",
|
||||
APK_IN_APEX_TESTAPEX_NAME, 1, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v1.apex");
|
||||
private static final TestApp TEST_APEX_WITH_APK_V2 = new TestApp("TestApexWithApkV2",
|
||||
APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v2.apex");
|
||||
private static final TestApp TEST_APP_A_V2_UNKNOWN = new TestApp("Av2Unknown", TestApp.A, 0,
|
||||
/*isApex*/false, "TestAppAv2.apk");
|
||||
|
||||
@Test
|
||||
public void testRollbackApexWithApk_Phase1() throws Exception {
|
||||
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
|
||||
InstallUtils.processUserData(TestApp.A);
|
||||
|
||||
int sessionId = Install.single(TEST_APEX_WITH_APK_V2).setStaged().setEnableRollback()
|
||||
.commit();
|
||||
InstallUtils.waitForSessionReady(sessionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRollbackApexWithApk_Phase2() throws Exception {
|
||||
assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2);
|
||||
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
|
||||
InstallUtils.processUserData(TestApp.A);
|
||||
|
||||
RollbackInfo available = RollbackUtils.getAvailableRollback(APK_IN_APEX_TESTAPEX_NAME);
|
||||
assertThat(available).isStaged();
|
||||
assertThat(available).packagesContainsExactly(
|
||||
Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1),
|
||||
Rollback.from(TEST_APP_A_V2_UNKNOWN).to(TestApp.A1));
|
||||
|
||||
RollbackUtils.rollback(available.getRollbackId(), TEST_APEX_WITH_APK_V2);
|
||||
RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId());
|
||||
assertThat(committed).isNotNull();
|
||||
assertThat(committed).isStaged();
|
||||
assertThat(committed).packagesContainsExactly(
|
||||
Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1),
|
||||
Rollback.from(TEST_APP_A_V2_UNKNOWN).to(TestApp.A1));
|
||||
assertThat(committed).causePackagesContainsExactly(TEST_APEX_WITH_APK_V2);
|
||||
assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
|
||||
|
||||
// Note: The app is not rolled back until after the rollback is staged
|
||||
// and the device has been rebooted.
|
||||
InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
|
||||
assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRollbackApexWithApk_Phase3() throws Exception {
|
||||
assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(1);
|
||||
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
|
||||
InstallUtils.processUserData(TestApp.A);
|
||||
}
|
||||
|
||||
private static void runShellCommand(String cmd) {
|
||||
ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation()
|
||||
.executeShellCommand(cmd);
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.tests.rollback.host;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.testng.Assert.assertThrows;
|
||||
|
||||
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
|
||||
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
|
||||
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
|
||||
|
||||
@@ -27,6 +28,7 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@@ -48,14 +50,32 @@ public class StagedRollbackTest extends BaseHostJUnit4Test {
|
||||
phase));
|
||||
}
|
||||
|
||||
private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
if (!getDevice().isAdbRoot()) {
|
||||
getDevice().enableAdbRoot();
|
||||
}
|
||||
getDevice().remountSystemWritable();
|
||||
getDevice().executeShellCommand(
|
||||
"rm -f /system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex "
|
||||
+ "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex");
|
||||
getDevice().reboot();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
runPhase("testCleanUp");
|
||||
|
||||
if (!getDevice().isAdbRoot()) {
|
||||
getDevice().enableAdbRoot();
|
||||
}
|
||||
getDevice().remountSystemWritable();
|
||||
getDevice().executeShellCommand(
|
||||
"rm -f /system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex "
|
||||
+ "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex");
|
||||
getDevice().reboot();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,6 +204,28 @@ public class StagedRollbackTest extends BaseHostJUnit4Test {
|
||||
runPhase("testRollbackDataPolicy_Phase3");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that userdata of apk-in-apex is restored when apex is rolled back.
|
||||
*/
|
||||
@Test
|
||||
public void testRollbackApexWithApk() throws Exception {
|
||||
getDevice().uninstallPackage("com.android.cts.install.lib.testapp.A");
|
||||
CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
|
||||
final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex";
|
||||
final File apex = buildHelper.getTestFile(fileName);
|
||||
if (!getDevice().isAdbRoot()) {
|
||||
getDevice().enableAdbRoot();
|
||||
}
|
||||
getDevice().remountSystemWritable();
|
||||
assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName));
|
||||
getDevice().reboot();
|
||||
runPhase("testRollbackApexWithApk_Phase1");
|
||||
getDevice().reboot();
|
||||
runPhase("testRollbackApexWithApk_Phase2");
|
||||
getDevice().reboot();
|
||||
runPhase("testRollbackApexWithApk_Phase3");
|
||||
}
|
||||
|
||||
private void crashProcess(String processName, int numberOfCrashes) throws Exception {
|
||||
String pid = "";
|
||||
String lastPid = "invalid";
|
||||
|
||||
8
tests/RollbackTest/testdata/AndroidManifest.xml
vendored
Normal file
8
tests/RollbackTest/testdata/AndroidManifest.xml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.apex.apkrollback.test">
|
||||
<!-- APEX does not have classes.dex -->
|
||||
<application android:hasCode="false" />
|
||||
<uses-sdk android:minSdkVersion="28" android:targetSdkVersion="29"/>
|
||||
</manifest>
|
||||
|
||||
4
tests/RollbackTest/testdata/manifest_v1.json
vendored
Normal file
4
tests/RollbackTest/testdata/manifest_v1.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "com.android.apex.apkrollback.test",
|
||||
"version": 1
|
||||
}
|
||||
4
tests/RollbackTest/testdata/manifest_v2.json
vendored
Normal file
4
tests/RollbackTest/testdata/manifest_v2.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "com.android.apex.apkrollback.test",
|
||||
"version": 2
|
||||
}
|
||||
Reference in New Issue
Block a user