blob: ece5a553fd954727415c00de5b963451001e8524 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.rollback;
import static com.android.server.rollback.RollbackManagerServiceImpl.sendFailure;
import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.ext.SdkExtensions;
import android.text.TextUtils;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseIntArray;
import android.util.SparseLongArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.ParseException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Information about a rollback available for a set of atomically installed packages.
*/
class Rollback {
private static final String TAG = "RollbackManager";
@IntDef(prefix = { "ROLLBACK_STATE_" }, value = {
ROLLBACK_STATE_ENABLING,
ROLLBACK_STATE_AVAILABLE,
ROLLBACK_STATE_COMMITTED,
ROLLBACK_STATE_DELETED
})
@Retention(RetentionPolicy.SOURCE)
@interface RollbackState {}
/**
* The rollback is in the process of being enabled. It is not yet
* available for use.
*/
static final int ROLLBACK_STATE_ENABLING = 0;
/**
* The rollback is currently available.
*/
static final int ROLLBACK_STATE_AVAILABLE = 1;
/**
* The rollback has been committed.
*/
static final int ROLLBACK_STATE_COMMITTED = 3;
/**
* The rollback has been deleted.
*/
static final int ROLLBACK_STATE_DELETED = 4;
/**
* The session ID for the staged session if this rollback data represents a staged session,
* {@code -1} otherwise.
*/
private final int mStagedSessionId;
/**
* The rollback info for this rollback.
*/
public final RollbackInfo info;
/**
* The directory where the rollback data is stored.
*/
private final File mBackupDir;
/**
* The time when the upgrade occurred, for purposes of expiring
* rollback data.
*
* The timestamp is not applicable for all rollback states, but we make
* sure to keep it non-null to avoid potential errors there.
*/
@GuardedBy("mLock")
private @NonNull Instant mTimestamp;
/**
* The current state of the rollback.
* ENABLING, AVAILABLE, or COMMITTED.
*/
@GuardedBy("mLock")
private @RollbackState int mState;
/**
* The id of the post-reboot apk session for a staged install, if any.
*/
@GuardedBy("mLock")
private int mApkSessionId = -1;
/**
* True if we are expecting the package manager to call restoreUserData
* for this rollback because it has just been committed but the rollback
* has not yet been fully applied.
*/
@GuardedBy("mLock")
private boolean mRestoreUserDataInProgress = false;
/**
* Lock object to guard all access to Rollback state.
*/
private final Object mLock = new Object();
/**
* The user that performed the install with rollback enabled.
*/
public final int mUserId;
/**
* The installer package name from the install session that enabled the rollback. May be null if
* that session did not set this value.
*
* If this is an empty string then the installer package name will be resolved by
* PackageManager.
*/
@Nullable public final String mInstallerPackageName;
/**
* Session ids for all packages in the install. For multi-package sessions, this is the list
* of child session ids. For normal sessions, this list is a single element with the normal
* session id.
*/
private final int[] mPackageSessionIds;
/**
* The number of sessions in the install which are notified with success by
* {@link PackageInstaller.SessionCallback#onFinished(int, boolean)}.
* This rollback will be enabled only after all child sessions finished with success.
*/
@GuardedBy("mLock")
private int mNumPackageSessionsWithSuccess;
/**
* The extension versions supported at the time of rollback creation.
*/
@NonNull private final SparseIntArray mExtensionVersions;
/**
* Constructs a new, empty Rollback instance.
*
* @param rollbackId the id of the rollback.
* @param backupDir the directory where the rollback data is stored.
* @param stagedSessionId the session id if this is a staged rollback, -1 otherwise.
* @param userId the user that performed the install with rollback enabled.
* @param installerPackageName the installer package name from the original install session.
* @param packageSessionIds the session ids for all packages in the install.
* @param extensionVersions the extension versions supported at the time of rollback creation
*/
Rollback(int rollbackId, File backupDir, int stagedSessionId, int userId,
String installerPackageName, int[] packageSessionIds,
SparseIntArray extensionVersions) {
this.info = new RollbackInfo(rollbackId,
/* packages */ new ArrayList<>(),
/* isStaged */ stagedSessionId != -1,
/* causePackages */ new ArrayList<>(),
/* committedSessionId */ -1);
mUserId = userId;
mInstallerPackageName = installerPackageName;
mBackupDir = backupDir;
mStagedSessionId = stagedSessionId;
mState = ROLLBACK_STATE_ENABLING;
mTimestamp = Instant.now();
mPackageSessionIds = packageSessionIds != null ? packageSessionIds : new int[0];
mExtensionVersions = Objects.requireNonNull(extensionVersions);
}
Rollback(int rollbackId, File backupDir, int stagedSessionId, int userId,
String installerPackageName) {
this(rollbackId, backupDir, stagedSessionId, userId, installerPackageName, null,
new SparseIntArray(0));
}
/**
* Constructs a pre-populated Rollback instance.
*/
Rollback(RollbackInfo info, File backupDir, Instant timestamp, int stagedSessionId,
@RollbackState int state, int apkSessionId, boolean restoreUserDataInProgress,
int userId, String installerPackageName, SparseIntArray extensionVersions) {
this.info = info;
mUserId = userId;
mInstallerPackageName = installerPackageName;
mBackupDir = backupDir;
mTimestamp = timestamp;
mStagedSessionId = stagedSessionId;
mState = state;
mApkSessionId = apkSessionId;
mRestoreUserDataInProgress = restoreUserDataInProgress;
mExtensionVersions = Objects.requireNonNull(extensionVersions);
// TODO(b/120200473): Include this field during persistence. This field will be used to
// decide which rollback to expire when ACTION_PACKAGE_REPLACED is received. Note persisting
// this field is not backward compatible. We won't fix b/120200473 until S to minimize the
// impact.
mPackageSessionIds = new int[0];
}
/**
* Whether the rollback is for rollback of a staged install.
*/
boolean isStaged() {
return info.isStaged();
}
/**
* Returns the directory in which rollback data should be stored.
*/
File getBackupDir() {
return mBackupDir;
}
/**
* Returns the time when the upgrade occurred, for purposes of expiring rollback data.
*/
Instant getTimestamp() {
synchronized (mLock) {
return mTimestamp;
}
}
/**
* Sets the time at which upgrade occurred.
*/
void setTimestamp(Instant timestamp) {
synchronized (mLock) {
mTimestamp = timestamp;
RollbackStore.saveRollback(this);
}
}
/**
* Returns the session ID for the staged session if this rollback data represents a staged
* session, or {@code -1} otherwise.
*/
int getStagedSessionId() {
return mStagedSessionId;
}
/**
* Returns the ID of the user that performed the install with rollback enabled.
*/
int getUserId() {
return mUserId;
}
/**
* Returns the installer package name from the install session that enabled the rollback. In the
* case that this is called on a rollback from an older version, returns the empty string.
*/
@Nullable String getInstallerPackageName() {
return mInstallerPackageName;
}
/**
* Returns the extension versions that were supported at the time that the rollback was created,
* as a mapping from SdkVersion to ExtensionVersion.
*/
SparseIntArray getExtensionVersions() {
return mExtensionVersions;
}
/**
* Returns true if the rollback is in the ENABLING state.
*/
boolean isEnabling() {
synchronized (mLock) {
return mState == ROLLBACK_STATE_ENABLING;
}
}
/**
* Returns true if the rollback is in the AVAILABLE state.
*/
boolean isAvailable() {
synchronized (mLock) {
return mState == ROLLBACK_STATE_AVAILABLE;
}
}
/**
* Returns true if the rollback is in the COMMITTED state.
*/
boolean isCommitted() {
synchronized (mLock) {
return mState == ROLLBACK_STATE_COMMITTED;
}
}
/**
* Returns true if the rollback is in the DELETED state.
*/
boolean isDeleted() {
synchronized (mLock) {
return mState == ROLLBACK_STATE_DELETED;
}
}
/**
* Saves this rollback to persistent storage.
*/
void saveRollback() {
synchronized (mLock) {
RollbackStore.saveRollback(this);
}
}
/**
* Enables this rollback for the provided package.
*
* @return boolean True if the rollback was enabled successfully for the specified package.
*/
boolean enableForPackage(String packageName, long newVersion, long installedVersion,
boolean isApex, String sourceDir, String[] splitSourceDirs, int rollbackDataPolicy) {
try {
RollbackStore.backupPackageCodePath(this, packageName, sourceDir);
if (!ArrayUtils.isEmpty(splitSourceDirs)) {
for (String dir : splitSourceDirs) {
RollbackStore.backupPackageCodePath(this, packageName, dir);
}
}
} catch (IOException e) {
Slog.e(TAG, "Unable to copy package for rollback for " + packageName, e);
return false;
}
PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(
new VersionedPackage(packageName, newVersion),
new VersionedPackage(packageName, installedVersion),
new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */,
isApex, false /* isApkInApex */, new IntArray(),
new SparseLongArray() /* ceSnapshotInodes */, rollbackDataPolicy);
synchronized (mLock) {
info.getPackages().add(packageRollbackInfo);
}
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/147666157): 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.
*/
void snapshotUserData(String packageName, int[] userIds, AppDataRollbackHelper dataHelper) {
synchronized (mLock) {
if (!isEnabling()) {
return;
}
for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
if (pkgRollbackInfo.getPackageName().equals(packageName)) {
if (pkgRollbackInfo.getRollbackDataPolicy()
== PackageManager.RollbackDataPolicy.RESTORE) {
dataHelper.snapshotAppData(info.getRollbackId(), pkgRollbackInfo, userIds);
pkgRollbackInfo.getSnapshottedUsers().addAll(IntArray.wrap(userIds));
RollbackStore.saveRollback(this);
}
break;
}
}
}
}
/**
* Commits the pending backups and restores for a given {@code userId}. If this rollback has a
* pending backup, it is updated with a mapping from {@code userId} to inode of the CE user data
* snapshot.
*/
void commitPendingBackupAndRestoreForUser(int userId, AppDataRollbackHelper dataHelper) {
synchronized (mLock) {
if (dataHelper.commitPendingBackupAndRestoreForUser(userId, this)) {
RollbackStore.saveRollback(this);
}
}
}
/**
* Changes the state of the rollback to AVAILABLE. This also changes the timestamp to the
* current time and saves the rollback. Does nothing if this rollback is already in the
* DELETED state.
*/
void makeAvailable() {
synchronized (mLock) {
if (isDeleted()) {
Slog.w(TAG, "Cannot make deleted rollback available.");
return;
}
mState = ROLLBACK_STATE_AVAILABLE;
mTimestamp = Instant.now();
RollbackStore.saveRollback(this);
}
}
/**
* Commits the rollback.
*/
void commit(final Context context, List<VersionedPackage> causePackages,
String callerPackageName, IntentSender statusReceiver) {
synchronized (mLock) {
if (!isAvailable()) {
sendFailure(context, statusReceiver,
RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
"Rollback unavailable");
return;
}
if (containsApex() && wasCreatedAtLowerExtensionVersion()) {
PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
if (extensionVersionReductionWouldViolateConstraint(mExtensionVersions, pmi)) {
sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
"Rollback may violate a minExtensionVersion constraint");
return;
}
}
// Get a context to use to install the downgraded version of the package.
Context pkgContext;
try {
pkgContext = context.createPackageContextAsUser(callerPackageName, 0,
UserHandle.of(mUserId));
} catch (PackageManager.NameNotFoundException e) {
sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
"Invalid callerPackageName");
return;
}
PackageManager pm = pkgContext.getPackageManager();
try {
PackageInstaller packageInstaller = pm.getPackageInstaller();
PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
parentParams.setRequestDowngrade(true);
parentParams.setMultiPackage();
if (isStaged()) {
parentParams.setStaged();
}
parentParams.setInstallReason(PackageManager.INSTALL_REASON_ROLLBACK);
int parentSessionId = packageInstaller.createSession(parentParams);
PackageInstaller.Session parentSession = packageInstaller.openSession(
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;
if (TextUtils.isEmpty(mInstallerPackageName)) {
installerPackageName = pm.getInstallerPackageName(
pkgRollbackInfo.getPackageName());
}
if (installerPackageName != null) {
params.setInstallerPackageName(installerPackageName);
}
params.setRequestDowngrade(true);
params.setRequiredInstalledVersionCode(
pkgRollbackInfo.getVersionRolledBackFrom().getLongVersionCode());
if (isStaged()) {
params.setStaged();
}
if (pkgRollbackInfo.isApex()) {
params.setInstallAsApex();
}
int sessionId = packageInstaller.createSession(params);
PackageInstaller.Session session = packageInstaller.openSession(sessionId);
File[] packageCodePaths = RollbackStore.getPackageCodePaths(
this, pkgRollbackInfo.getPackageName());
if (packageCodePaths == null) {
sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
"Backup copy of package: "
+ pkgRollbackInfo.getPackageName() + " is inaccessible");
return;
}
for (File packageCodePath : packageCodePaths) {
try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(packageCodePath,
ParcelFileDescriptor.MODE_READ_ONLY)) {
final long token = Binder.clearCallingIdentity();
try {
session.write(packageCodePath.getName(), 0,
packageCodePath.length(),
fd);
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
parentSession.addChildSessionId(sessionId);
}
final LocalIntentReceiver receiver = new LocalIntentReceiver(
(Intent result) -> {
int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
if (status != PackageInstaller.STATUS_SUCCESS) {
// Committing the rollback failed, but we still have all the info we
// need to try rolling back again, so restore the rollback state to
// how it was before we tried committing.
// TODO: Should we just kill this rollback if commit failed?
// Why would we expect commit not to fail again?
// TODO: Could this cause a rollback to be resurrected
// if it should otherwise have expired by now?
synchronized (mLock) {
mState = ROLLBACK_STATE_AVAILABLE;
mRestoreUserDataInProgress = false;
info.setCommittedSessionId(-1);
}
sendFailure(context, statusReceiver,
RollbackManager.STATUS_FAILURE_INSTALL,
"Rollback downgrade install failed: "
+ result.getStringExtra(
PackageInstaller.EXTRA_STATUS_MESSAGE));
return;
}
synchronized (mLock) {
if (!isStaged()) {
// All calls to restoreUserData should have
// completed by now for a non-staged install.
mRestoreUserDataInProgress = false;
}
info.getCausePackages().addAll(causePackages);
RollbackStore.deletePackageCodePaths(this);
RollbackStore.saveRollback(this);
}
// Send success.
try {
final Intent fillIn = new Intent();
fillIn.putExtra(
RollbackManager.EXTRA_STATUS,
RollbackManager.STATUS_SUCCESS);
statusReceiver.sendIntent(context, 0, fillIn, null, null);
} catch (IntentSender.SendIntentException e) {
// Nowhere to send the result back to, so don't bother.
}
Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
for (UserInfo userInfo : UserManager.get(context).getUsers(true)) {
context.sendBroadcastAsUser(broadcast,
userInfo.getUserHandle(),
Manifest.permission.MANAGE_ROLLBACKS);
}
}
);
mState = ROLLBACK_STATE_COMMITTED;
info.setCommittedSessionId(parentSessionId);
mRestoreUserDataInProgress = true;
parentSession.commit(receiver.getIntentSender());
} catch (IOException e) {
Slog.e(TAG, "Rollback failed", e);
sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
"IOException: " + e.toString());
}
}
}
/**
* Restores user data for the specified package if this rollback is currently marked as
* having a restore in progress.
*
* @return boolean True if this rollback has a restore in progress and contains the specified
* package.
*/
boolean restoreUserDataForPackageIfInProgress(String packageName, int[] userIds, int appId,
String seInfo, AppDataRollbackHelper dataHelper) {
synchronized (mLock) {
if (!isRestoreUserDataInProgress()) {
return false;
}
boolean foundPackage = false;
for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
if (pkgRollbackInfo.getPackageName().equals(packageName)) {
foundPackage = true;
boolean changedRollback = false;
for (int userId : userIds) {
changedRollback |= dataHelper.restoreAppData(
info.getRollbackId(), pkgRollbackInfo, userId, appId, seInfo);
}
// We've updated metadata about this rollback, so save it to flash.
if (changedRollback) {
RollbackStore.saveRollback(this);
}
break;
}
}
return foundPackage;
}
}
/**
* Deletes app data snapshots associated with this rollback, and moves to the DELETED state.
*/
void delete(AppDataRollbackHelper dataHelper) {
synchronized (mLock) {
boolean containsApex = false;
for (PackageRollbackInfo pkgInfo : info.getPackages()) {
if (pkgInfo.isApex()) {
containsApex = true;
} else {
IntArray snapshottedUsers = pkgInfo.getSnapshottedUsers();
for (int i = 0; i < snapshottedUsers.size(); i++) {
// Destroy app data snapshot.
int userId = snapshottedUsers.get(i);
dataHelper.destroyAppDataSnapshot(info.getRollbackId(), pkgInfo, userId);
}
}
}
if (containsApex) {
dataHelper.destroyApexDeSnapshots(info.getRollbackId());
}
RollbackStore.deleteRollback(this);
mState = ROLLBACK_STATE_DELETED;
}
}
/**
* Returns the id of the post-reboot apk session for a staged install, if any.
*/
int getApkSessionId() {
synchronized (mLock) {
return mApkSessionId;
}
}
/**
* Sets the id of the post-reboot apk session for a staged install.
*/
void setApkSessionId(int apkSessionId) {
synchronized (mLock) {
mApkSessionId = apkSessionId;
RollbackStore.saveRollback(this);
}
}
/**
* Returns true if we are expecting the package manager to call restoreUserData for this
* rollback because it has just been committed but the rollback has not yet been fully applied.
*/
boolean isRestoreUserDataInProgress() {
synchronized (mLock) {
return mRestoreUserDataInProgress;
}
}
/**
* Sets whether we are expecting the package manager to call restoreUserData for this
* rollback because it has just been committed but the rollback has not yet been fully applied.
*/
void setRestoreUserDataInProgress(boolean restoreUserDataInProgress) {
synchronized (mLock) {
mRestoreUserDataInProgress = restoreUserDataInProgress;
RollbackStore.saveRollback(this);
}
}
/**
* Returns true if this rollback includes the package with the provided {@code packageName}.
*/
boolean includesPackage(String packageName) {
synchronized (mLock) {
for (PackageRollbackInfo packageRollbackInfo : info.getPackages()) {
if (packageRollbackInfo.getPackageName().equals(packageName)) {
return true;
}
}
return false;
}
}
/**
* Returns true if this rollback includes the package with the provided {@code packageName}
* with a <i>version rolled back from</i> that is not {@code versionCode}.
*/
boolean includesPackageWithDifferentVersion(String packageName, long versionCode) {
synchronized (mLock) {
for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
if (pkgRollbackInfo.getPackageName().equals(packageName)
&& pkgRollbackInfo.getVersionRolledBackFrom().getLongVersionCode()
!= versionCode) {
return true;
}
}
return false;
}
}
/**
* Returns a list containing the names of all the packages included in this rollback.
*/
List<String> getPackageNames() {
synchronized (mLock) {
List<String> result = new ArrayList<>();
for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
result.add(pkgRollbackInfo.getPackageName());
}
return result;
}
}
/**
* Returns a list containing the names of all the apex packages included in this rollback.
*/
List<String> getApexPackageNames() {
synchronized (mLock) {
List<String> result = new ArrayList<>();
for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
if (pkgRollbackInfo.isApex()) {
result.add(pkgRollbackInfo.getPackageName());
}
}
return result;
}
}
/**
* Returns true if this rollback contains the provided {@code packageSessionId}.
*/
boolean containsSessionId(int packageSessionId) {
for (int id : mPackageSessionIds) {
if (id == packageSessionId) {
return true;
}
}
return false;
}
/**
* Called when a child session finished with success.
* Returns true when all child sessions are notified with success. This rollback will be
* enabled only after all child sessions finished with success.
*/
boolean notifySessionWithSuccess() {
synchronized (mLock) {
return ++mNumPackageSessionsWithSuccess == mPackageSessionIds.length;
}
}
/**
* Returns true if all packages in this rollback are enabled. We won't enable this rollback
* until all packages are enabled. Note we don't count apk-in-apex here since they are enabled
* automatically when the embedding apex is enabled.
*/
boolean allPackagesEnabled() {
synchronized (mLock) {
int packagesWithoutApkInApex = 0;
for (PackageRollbackInfo rollbackInfo : info.getPackages()) {
if (!rollbackInfo.isApkInApex()) {
packagesWithoutApkInApex++;
}
}
return packagesWithoutApkInApex == mPackageSessionIds.length;
}
}
static String rollbackStateToString(@RollbackState int state) {
switch (state) {
case Rollback.ROLLBACK_STATE_ENABLING: return "enabling";
case Rollback.ROLLBACK_STATE_AVAILABLE: return "available";
case Rollback.ROLLBACK_STATE_COMMITTED: return "committed";
}
throw new AssertionError("Invalid rollback state: " + state);
}
static @RollbackState int rollbackStateFromString(String state)
throws ParseException {
switch (state) {
case "enabling": return Rollback.ROLLBACK_STATE_ENABLING;
case "available": return Rollback.ROLLBACK_STATE_AVAILABLE;
case "committed": return Rollback.ROLLBACK_STATE_COMMITTED;
}
throw new ParseException("Invalid rollback state: " + state, 0);
}
String getStateAsString() {
synchronized (mLock) {
return rollbackStateToString(mState);
}
}
/**
* Returns true if there is an app installed that specifies a minExtensionVersion greater
* than what was present at the time this Rollback was created.
*/
@VisibleForTesting
static boolean extensionVersionReductionWouldViolateConstraint(
SparseIntArray rollbackExtVers, PackageManagerInternal pmi) {
if (rollbackExtVers.size() == 0) {
return false;
}
List<String> packages = pmi.getPackageList().getPackageNames();
for (int i = 0; i < packages.size(); i++) {
AndroidPackage pkg = pmi.getPackage(packages.get(i));
SparseIntArray minExtVers = pkg.getMinExtensionVersions();
if (minExtVers == null) {
continue;
}
for (int j = 0; j < rollbackExtVers.size(); j++) {
int minExt = minExtVers.get(rollbackExtVers.keyAt(j), -1);
if (rollbackExtVers.valueAt(j) < minExt) {
return true;
}
}
}
return false;
}
/**
* Returns true if for any SDK version, the extension version recorded at the time of rollback
* creation is lower than the current extension version.
*/
private boolean wasCreatedAtLowerExtensionVersion() {
for (int i = 0; i < mExtensionVersions.size(); i++) {
if (SdkExtensions.getExtensionVersion(mExtensionVersions.keyAt(i))
> mExtensionVersions.valueAt(i)) {
return true;
}
}
return false;
}
private boolean containsApex() {
for (PackageRollbackInfo pkgInfo : info.getPackages()) {
if (pkgInfo.isApex()) {
return true;
}
}
return false;
}
void dump(IndentingPrintWriter ipw) {
synchronized (mLock) {
ipw.println(info.getRollbackId() + ":");
ipw.increaseIndent();
ipw.println("-state: " + getStateAsString());
ipw.println("-timestamp: " + getTimestamp());
if (getStagedSessionId() != -1) {
ipw.println("-stagedSessionId: " + getStagedSessionId());
}
ipw.println("-packages:");
ipw.increaseIndent();
for (PackageRollbackInfo pkg : info.getPackages()) {
ipw.println(pkg.getPackageName()
+ " " + pkg.getVersionRolledBackFrom().getLongVersionCode()
+ " -> " + pkg.getVersionRolledBackTo().getLongVersionCode());
}
ipw.decreaseIndent();
if (isCommitted()) {
ipw.println("-causePackages:");
ipw.increaseIndent();
for (VersionedPackage cPkg : info.getCausePackages()) {
ipw.println(cPkg.getPackageName() + " " + cPkg.getLongVersionCode());
}
ipw.decreaseIndent();
ipw.println("-committedSessionId: " + info.getCommittedSessionId());
}
if (mExtensionVersions.size() > 0) {
ipw.println("-extensionVersions:");
ipw.increaseIndent();
ipw.println(mExtensionVersions.toString());
ipw.decreaseIndent();
}
ipw.decreaseIndent();
}
}
}