summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Mohammad Samiul Islam <samiul@google.com> 2019-12-20 17:46:01 +0000
committer Mohammad Samiul Islam <samiul@google.com> 2020-01-10 16:15:47 +0000
commit3fcecfc1ce98969c11be853a39ecd08f3ed646d7 (patch)
tree7fc6421d038e32de1589fe710db62de671644c0d
parent5ea8b0d72f47d4c2e028992294a8904a3945715e (diff)
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
-rw-r--r--core/java/android/content/rollback/PackageRollbackInfo.java19
-rw-r--r--services/core/java/android/content/pm/PackageManagerInternal.java6
-rw-r--r--services/core/java/com/android/server/pm/ApexManager.java145
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java7
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java20
-rw-r--r--services/core/java/com/android/server/pm/StagingManager.java95
-rw-r--r--services/core/java/com/android/server/rollback/Rollback.java36
-rw-r--r--services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java50
-rw-r--r--services/core/java/com/android/server/rollback/RollbackStore.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java14
-rw-r--r--services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java3
-rw-r--r--tests/RollbackTest/Android.bp45
-rw-r--r--tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java55
-rw-r--r--tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java42
-rw-r--r--tests/RollbackTest/testdata/AndroidManifest.xml8
-rw-r--r--tests/RollbackTest/testdata/manifest_v1.json4
-rw-r--r--tests/RollbackTest/testdata/manifest_v2.json4
18 files changed, 500 insertions, 61 deletions
diff --git a/core/java/android/content/rollback/PackageRollbackInfo.java b/core/java/android/content/rollback/PackageRollbackInfo.java
index 6378db0ebbbd..b273cd67479c 100644
--- a/core/java/android/content/rollback/PackageRollbackInfo.java
+++ b/core/java/android/content/rollback/PackageRollbackInfo.java
@@ -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 =
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 312dd46fbc73..76572d35d516 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -811,6 +811,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}.
*
* @param packageName apex package to uninstall.
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 307a07bb09a2..a009183f7ecb 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -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) {
+ this(null, apexDirectory, preInstalledApexPath);
+ }
- private ActiveApexInfo(File apexDirectory, File 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));
}
}
@@ -232,6 +255,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.
*
* @param pw the {@link PrintWriter} object to send information to.
@@ -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
@@ -721,6 +810,16 @@ abstract class ApexManager {
}
@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
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index c43f23454ca6..6331dd46c035 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -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() {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a9571d97200c..52e8e37cc3f9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -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;
@@ -23452,6 +23459,11 @@ public class PackageManagerService extends IPackageManager.Stub
}
@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) {
final int callerUid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 9e462cd529bb..2265d010216e 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -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());
}
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 88c1564fdb60..d719afb70796 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -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);
@@ -334,6 +334,30 @@ class Rollback {
}
/**
+ * 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;
}
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index e29d1a765d69..2a59af3bee61 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -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) {
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index df75a29edd79..bbcd0def05a8 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -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)
diff --git a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
index a83d94001cf8..f871203728c0 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
@@ -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());
}
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
index 757a884f8ded..d0d2edc59861 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
@@ -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(
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
index e368d634b968..164c88382828 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
@@ -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
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index 2bc129ae4840..091edd4dc0d9 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -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,
+} \ No newline at end of file
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 9e490f765eab..3877cc139a3e 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -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);
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 91577c202df9..6daa6bc723c4 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -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";
diff --git a/tests/RollbackTest/testdata/AndroidManifest.xml b/tests/RollbackTest/testdata/AndroidManifest.xml
new file mode 100644
index 000000000000..f21ec899eb69
--- /dev/null
+++ b/tests/RollbackTest/testdata/AndroidManifest.xml
@@ -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>
+
diff --git a/tests/RollbackTest/testdata/manifest_v1.json b/tests/RollbackTest/testdata/manifest_v1.json
new file mode 100644
index 000000000000..1762fc6764cf
--- /dev/null
+++ b/tests/RollbackTest/testdata/manifest_v1.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.apex.apkrollback.test",
+ "version": 1
+}
diff --git a/tests/RollbackTest/testdata/manifest_v2.json b/tests/RollbackTest/testdata/manifest_v2.json
new file mode 100644
index 000000000000..c5127b9c3023
--- /dev/null
+++ b/tests/RollbackTest/testdata/manifest_v2.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.apex.apkrollback.test",
+ "version": 2
+}