Merge changes Ic2bb4798,I2a409de2,I88a50a0e,Ia69342a4,Ia6548e4d, ...

* changes:
  Use "commit" instead of "execute" for rollbacks.
  Rename PACKAGE_ROLLBACK_EXECUTED to ROLLBACK_COMMITTED.
  Remove unused RollbackManager APIs.
  Use RollbackManager.getAvailableRollbacks in RollbackTest
  Use new RollbackManager API in RollbackPackageHealthObserver.
  Add RollbackManager.getAvailableRollbacks API.
  Include all relevant packages in RollbackInfo.
This commit is contained in:
Richard Uhler
2019-01-26 07:31:28 +00:00
committed by Android (Google) Code Review
12 changed files with 266 additions and 302 deletions

View File

@@ -1303,13 +1303,13 @@ package android.content {
field public static final String ACTION_MANAGE_PERMISSION_APPS = "android.intent.action.MANAGE_PERMISSION_APPS";
field @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public static final String ACTION_MANAGE_SPECIAL_APP_ACCESSES = "android.intent.action.MANAGE_SPECIAL_APP_ACCESSES";
field public static final String ACTION_MASTER_CLEAR_NOTIFICATION = "android.intent.action.MASTER_CLEAR_NOTIFICATION";
field public static final String ACTION_PACKAGE_ROLLBACK_EXECUTED = "android.intent.action.PACKAGE_ROLLBACK_EXECUTED";
field public static final String ACTION_PRE_BOOT_COMPLETED = "android.intent.action.PRE_BOOT_COMPLETED";
field public static final String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
field public static final String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE";
field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_REVIEW_APP_PERMISSION_USAGE = "android.intent.action.REVIEW_APP_PERMISSION_USAGE";
field public static final String ACTION_REVIEW_PERMISSIONS = "android.intent.action.REVIEW_PERMISSIONS";
field public static final String ACTION_REVIEW_PERMISSION_USAGE = "android.intent.action.REVIEW_PERMISSION_USAGE";
field public static final String ACTION_ROLLBACK_COMMITTED = "android.intent.action.ROLLBACK_COMMITTED";
field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS";
field @Deprecated public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
field public static final String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED";
@@ -1695,18 +1695,17 @@ package android.content.rollback {
public final class RollbackInfo implements android.os.Parcelable {
method public int describeContents();
method public java.util.List<android.content.rollback.PackageRollbackInfo> getPackages();
method public int getRollbackId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.content.rollback.RollbackInfo> CREATOR;
field public final android.content.rollback.PackageRollbackInfo targetPackage;
}
public final class RollbackManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void executeRollback(@NonNull android.content.rollback.RollbackInfo, @NonNull android.content.IntentSender);
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void commitRollback(@NonNull android.content.rollback.RollbackInfo, @NonNull android.content.IntentSender);
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void expireRollbackForPackage(@NonNull String);
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @Nullable public android.content.rollback.RollbackInfo getAvailableRollback(@NonNull String);
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<java.lang.String> getPackagesWithAvailableRollbacks();
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<android.content.rollback.RollbackInfo> getRecentlyExecutedRollbacks();
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public java.util.List<android.content.rollback.RollbackInfo> getAvailableRollbacks();
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<android.content.rollback.RollbackInfo> getRecentlyCommittedRollbacks();
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void reloadPersistedData();
}

View File

@@ -2375,8 +2375,7 @@ public class Intent implements Parcelable, Cloneable {
public static final String ACTION_PACKAGE_ENABLE_ROLLBACK =
"android.intent.action.PACKAGE_ENABLE_ROLLBACK";
/**
* Broadcast Action: An existing version of an application package has been
* rolled back to a previous version.
* Broadcast Action: A rollback has been committed.
*
* <p class="note">This is a protected intent that can only be sent
* by the system.
@@ -2385,8 +2384,8 @@ public class Intent implements Parcelable, Cloneable {
*/
@SystemApi
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PACKAGE_ROLLBACK_EXECUTED =
"android.intent.action.PACKAGE_ROLLBACK_EXECUTED";
public static final String ACTION_ROLLBACK_COMMITTED =
"android.intent.action.ROLLBACK_COMMITTED";
/**
* @hide
* Broadcast Action: Ask system services if there is any reason to

View File

@@ -17,17 +17,13 @@
package android.content.rollback;
import android.content.pm.ParceledListSlice;
import android.content.pm.StringParceledListSlice;
import android.content.rollback.RollbackInfo;
import android.content.IntentSender;
/** {@hide} */
interface IRollbackManager {
RollbackInfo getAvailableRollback(String packageName);
StringParceledListSlice getPackagesWithAvailableRollbacks();
ParceledListSlice getAvailableRollbacks();
ParceledListSlice getRecentlyExecutedRollbacks();
void executeRollback(in RollbackInfo rollback, String callerPackageName,

View File

@@ -20,6 +20,8 @@ import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.List;
/**
* Information about a set of packages that can be, or already have been
* rolled back together.
@@ -34,25 +36,20 @@ public final class RollbackInfo implements Parcelable {
*/
private final int mRollbackId;
/**
* The package that needs to be rolled back.
*/
public final PackageRollbackInfo targetPackage;
private final List<PackageRollbackInfo> mPackages;
// TODO: Add a list of additional packages rolled back due to atomic
// install dependencies when rollback of atomic installs is supported.
// TODO: Add a flag to indicate if reboot is required, when rollback of
// staged installs is supported.
/** @hide */
public RollbackInfo(int rollbackId, PackageRollbackInfo targetPackage) {
public RollbackInfo(int rollbackId, List<PackageRollbackInfo> packages) {
this.mRollbackId = rollbackId;
this.targetPackage = targetPackage;
this.mPackages = packages;
}
private RollbackInfo(Parcel in) {
mRollbackId = in.readInt();
targetPackage = PackageRollbackInfo.CREATOR.createFromParcel(in);
mPackages = in.createTypedArrayList(PackageRollbackInfo.CREATOR);
}
/**
@@ -62,6 +59,13 @@ public final class RollbackInfo implements Parcelable {
return mRollbackId;
}
/**
* Returns the list of package that are rolled back.
*/
public List<PackageRollbackInfo> getPackages() {
return mPackages;
}
@Override
public int describeContents() {
return 0;
@@ -70,7 +74,7 @@ public final class RollbackInfo implements Parcelable {
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mRollbackId);
targetPackage.writeToParcel(out, flags);
out.writeTypedList(mPackages);
}
public static final Parcelable.Creator<RollbackInfo> CREATOR =

View File

@@ -17,7 +17,6 @@
package android.content.rollback;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -50,55 +49,26 @@ public final class RollbackManager {
}
/**
* Returns the rollback currently available to be executed for the given
* package.
* <p>
* The returned RollbackInfo describes what packages would be rolled back,
* including package version codes before and after rollback. The rollback
* can be initiated using {@link #executeRollback(RollbackInfo,IntentSender)}.
* <p>
* TODO: What if there is no package installed on device for packageName?
* Returns a list of all currently available rollbacks.
*
* @param packageName name of the package to get the availble RollbackInfo for.
* @return the rollback available for the package, or null if no rollback
* is available for the package.
* @throws SecurityException if the caller does not have the
* MANAGE_ROLLBACKS permission.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
public @Nullable RollbackInfo getAvailableRollback(@NonNull String packageName) {
public List<RollbackInfo> getAvailableRollbacks() {
try {
return mBinder.getAvailableRollback(packageName);
return mBinder.getAvailableRollbacks().getList();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Gets the names of packages that are available for rollback.
* Call {@link #getAvailableRollback(String)} to get more information
* about the rollback available for a particular package.
*
* @return the names of packages that are available for rollback.
* @throws SecurityException if the caller does not have the
* MANAGE_ROLLBACKS permission.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
public @NonNull List<String> getPackagesWithAvailableRollbacks() {
try {
return mBinder.getPackagesWithAvailableRollbacks().getList();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Gets the list of all recently executed rollbacks.
* Gets the list of all recently committed rollbacks.
* This is for the purposes of preventing re-install of a bad version of a
* package.
* package and monitoring the status of a staged rollback.
* <p>
* Returns an empty list if there are no recently executed rollbacks.
* Returns an empty list if there are no recently committed rollbacks.
* <p>
* To avoid having to keep around complete rollback history forever on a
* device, the returned list of rollbacks is only guaranteed to include
@@ -107,12 +77,12 @@ public final class RollbackManager {
* (without the possibility of rollback) to a higher version code than was
* rolled back from.
*
* @return the recently executed rollbacks
* @return the recently committed rollbacks
* @throws SecurityException if the caller does not have the
* MANAGE_ROLLBACKS permission.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
public @NonNull List<RollbackInfo> getRecentlyExecutedRollbacks() {
public @NonNull List<RollbackInfo> getRecentlyCommittedRollbacks() {
try {
return mBinder.getRecentlyExecutedRollbacks().getList();
} catch (RemoteException e) {
@@ -121,25 +91,24 @@ public final class RollbackManager {
}
/**
* Execute the given rollback, rolling back all versions of the packages
* to the last good versions previously installed on the device as
* specified in the given rollback object. The rollback will fail if any
* of the installed packages or available rollbacks are inconsistent with
* the versions specified in the given rollback object, which can happen
* if a package has been updated or a rollback expired since the rollback
* object was retrieved from {@link #getAvailableRollback(String)}.
* Commit the rollback with given id, rolling back all versions of the
* packages to the last good versions previously installed on the device
* as specified in the corresponding RollbackInfo object. The
* rollback will fail if any of the installed packages or available
* rollbacks are inconsistent with the versions specified in the given
* rollback object, which can happen if a package has been updated or a
* rollback expired since the rollback object was retrieved from
* {@link #getAvailableRollbacks()}.
* <p>
* TODO: Specify the returns status codes.
* TODO: What happens in case reboot is required for the rollback to take
* effect for staged installs?
*
* @param rollback to execute
* @param rollback to commit
* @param statusReceiver where to deliver the results
* @throws SecurityException if the caller does not have the
* MANAGE_ROLLBACKS permission.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
public void executeRollback(@NonNull RollbackInfo rollback,
public void commitRollback(@NonNull RollbackInfo rollback,
@NonNull IntentSender statusReceiver) {
try {
mBinder.executeRollback(rollback, mCallerPackageName, statusReceiver);

View File

@@ -43,7 +43,7 @@
<protected-broadcast android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_CHANGED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_ENABLE_ROLLBACK" />
<protected-broadcast android:name="android.intent.action.PACKAGE_ROLLBACK_EXECUTED" />
<protected-broadcast android:name="android.intent.action.ROLLBACK_COMMITTED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_RESTARTED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_DATA_CLEARED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_FIRST_LAUNCH" />

View File

@@ -28,7 +28,6 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageParser;
import android.content.pm.ParceledListSlice;
import android.content.pm.StringParceledListSlice;
import android.content.pm.VersionedPackage;
import android.content.rollback.IRollbackManager;
import android.content.rollback.PackageRollbackInfo;
@@ -56,12 +55,10 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
/**
* Implementation of service that manages APK level rollbacks.
@@ -200,48 +197,20 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
}
@Override
public RollbackInfo getAvailableRollback(String packageName) {
public ParceledListSlice getAvailableRollbacks() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_ROLLBACKS,
"getAvailableRollback");
"getAvailableRollbacks");
RollbackData data = getRollbackForPackage(packageName);
if (data == null) {
return null;
}
// Note: The rollback for the package ought to be for the currently
// installed version, otherwise the rollback data is out of date. In
// that rare case, we'll check when we execute the rollback whether
// it's out of date or not, so no need to check package versions here.
for (PackageRollbackInfo info : data.packages) {
if (info.getPackageName().equals(packageName)) {
// TODO: Once the RollbackInfo API supports info about
// dependant packages, add that info here.
return new RollbackInfo(data.rollbackId, info);
}
}
return null;
}
@Override
public StringParceledListSlice getPackagesWithAvailableRollbacks() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_ROLLBACKS,
"getPackagesWithAvailableRollbacks");
final Set<String> packageNames = new HashSet<>();
synchronized (mLock) {
ensureRollbackDataLoadedLocked();
List<RollbackInfo> rollbacks = new ArrayList<>();
for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
RollbackData data = mAvailableRollbacks.get(i);
for (PackageRollbackInfo info : data.packages) {
packageNames.add(info.getPackageName());
}
rollbacks.add(new RollbackInfo(data.rollbackId, data.packages));
}
return new ParceledListSlice<>(rollbacks);
}
return new StringParceledListSlice(new ArrayList<>(packageNames));
}
@Override
@@ -279,18 +248,11 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
*/
private void executeRollbackInternal(RollbackInfo rollback,
String callerPackageName, IntentSender statusReceiver) {
String targetPackageName = rollback.targetPackage.getPackageName();
Log.i(TAG, "Initiating rollback of " + targetPackageName);
Log.i(TAG, "Initiating rollback");
// Get the latest RollbackData for the target package.
final RollbackData data = getRollbackForPackage(targetPackageName);
RollbackData data = getRollbackForId(rollback.getRollbackId());
if (data == null) {
sendFailure(statusReceiver, "No rollback available for package.");
return;
}
if (data.rollbackId != rollback.getRollbackId()) {
sendFailure(statusReceiver, "Rollback for package is out of date.");
sendFailure(statusReceiver, "Rollback unavailable");
return;
}
@@ -335,14 +297,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
PackageManager pm = context.getPackageManager();
try {
PackageInstaller packageInstaller = pm.getPackageInstaller();
String installerPackageName = pm.getInstallerPackageName(targetPackageName);
if (installerPackageName == null) {
sendFailure(statusReceiver, "Cannot find installer package");
return;
}
PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
parentParams.setInstallerPackageName(installerPackageName);
parentParams.setAllowDowngrade(true);
parentParams.setMultiPackage();
int parentSessionId = packageInstaller.createSession(parentParams);
@@ -351,6 +307,11 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
for (PackageRollbackInfo info : data.packages) {
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
String installerPackageName = pm.getInstallerPackageName(info.getPackageName());
if (installerPackageName == null) {
sendFailure(statusReceiver, "Cannot find installer package");
return;
}
params.setInstallerPackageName(installerPackageName);
params.setAllowDowngrade(true);
int sessionId = packageInstaller.createSession(params);
@@ -392,7 +353,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
addRecentlyExecutedRollback(rollback);
sendSuccess(statusReceiver);
Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
// TODO: This call emits the warning "Calling a method in the
// system process without a qualified user". Fix that.
@@ -406,7 +367,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
data.inProgress = true;
parentSession.commit(receiver.getIntentSender());
} catch (IOException e) {
Log.e(TAG, "Unable to roll back " + targetPackageName, e);
Log.e(TAG, "Rollback failed", e);
sendFailure(statusReceiver, "IOException: " + e.toString());
return;
}
@@ -537,9 +498,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
boolean changed = false;
while (iter.hasNext()) {
RollbackInfo rollback = iter.next();
if (packageName.equals(rollback.targetPackage.getPackageName())) {
iter.remove();
changed = true;
for (PackageRollbackInfo info : rollback.getPackages()) {
if (packageName.equals(info.getPackageName())) {
iter.remove();
changed = true;
break;
}
}
}
@@ -935,6 +899,25 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
return null;
}
/*
* Returns the RollbackData, if any, for an available rollback with the
* given rollbackId.
*/
private RollbackData getRollbackForId(int rollbackId) {
synchronized (mLock) {
// TODO: Have ensureRollbackDataLoadedLocked return the list of
// available rollbacks, to hopefully avoid forgetting to call it?
ensureRollbackDataLoadedLocked();
for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
RollbackData data = mAvailableRollbacks.get(i);
if (data.rollbackId == rollbackId) {
return data;
}
}
}
return null;
}
@GuardedBy("mLock")
private int allocateRollbackIdLocked() throws IOException {
int n = 0;

View File

@@ -19,6 +19,7 @@ package com.android.server.rollback;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInstaller;
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
import android.os.Handler;
@@ -51,11 +52,14 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
@Override
public boolean onHealthCheckFailed(String packageName) {
RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
RollbackInfo rollback = rollbackManager.getAvailableRollback(packageName);
if (rollback != null) {
// TODO(zezeozue): Only rollback if rollback version == failed package version
mHandler.post(() -> executeRollback(rollbackManager, rollback));
return true;
for (RollbackInfo rollback : rollbackManager.getAvailableRollbacks()) {
for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
if (packageName.equals(packageRollback.getPackageName())) {
// TODO(zezeozue): Only rollback if rollback version == failed package version
mHandler.post(() -> executeRollback(rollbackManager, rollback));
return true;
}
}
}
// Don't handle the notification, no rollbacks available
return false;
@@ -84,7 +88,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
}
});
});
manager.executeRollback(rollback, rollbackReceiver.getIntentSender());
manager.commitRollback(rollback, rollbackReceiver.getIntentSender());
}
@Override

View File

@@ -114,13 +114,9 @@ class RollbackStore {
for (int i = 0; i < array.length(); ++i) {
JSONObject element = array.getJSONObject(i);
int rollbackId = element.getInt("rollbackId");
String packageName = element.getString("packageName");
long higherVersionCode = element.getLong("higherVersionCode");
long lowerVersionCode = element.getLong("lowerVersionCode");
PackageRollbackInfo target = new PackageRollbackInfo(
new VersionedPackage(packageName, higherVersionCode),
new VersionedPackage(packageName, lowerVersionCode));
RollbackInfo rollback = new RollbackInfo(rollbackId, target);
List<PackageRollbackInfo> packages = packageRollbackInfosFromJson(
element.getJSONArray("packages"));
RollbackInfo rollback = new RollbackInfo(rollbackId, packages);
recentlyExecutedRollbacks.add(rollback);
}
} catch (IOException | JSONException e) {
@@ -155,18 +151,8 @@ class RollbackStore {
void saveAvailableRollback(RollbackData data) throws IOException {
try {
JSONObject dataJson = new JSONObject();
JSONArray packagesJson = new JSONArray();
for (PackageRollbackInfo info : data.packages) {
JSONObject infoJson = new JSONObject();
infoJson.put("packageName", info.getPackageName());
infoJson.put("higherVersionCode",
info.getVersionRolledBackFrom().getLongVersionCode());
infoJson.put("lowerVersionCode",
info.getVersionRolledBackTo().getVersionCode());
packagesJson.put(infoJson);
}
dataJson.put("rollbackId", data.rollbackId);
dataJson.put("packages", packagesJson);
dataJson.put("packages", toJson(data.packages));
dataJson.put("timestamp", data.timestamp.toString());
PrintWriter pw = new PrintWriter(new File(data.backupDir, "rollback.json"));
@@ -200,11 +186,7 @@ class RollbackStore {
RollbackInfo rollback = recentlyExecutedRollbacks.get(i);
JSONObject element = new JSONObject();
element.put("rollbackId", rollback.getRollbackId());
element.put("packageName", rollback.targetPackage.getPackageName());
element.put("higherVersionCode",
rollback.targetPackage.getVersionRolledBackFrom().getLongVersionCode());
element.put("lowerVersionCode",
rollback.targetPackage.getVersionRolledBackTo().getLongVersionCode());
element.put("packages", toJson(rollback.getPackages()));
array.put(element);
}
@@ -229,18 +211,7 @@ class RollbackStore {
int rollbackId = dataJson.getInt("rollbackId");
RollbackData data = new RollbackData(rollbackId, backupDir);
JSONArray packagesJson = dataJson.getJSONArray("packages");
for (int i = 0; i < packagesJson.length(); ++i) {
JSONObject infoJson = packagesJson.getJSONObject(i);
String packageName = infoJson.getString("packageName");
long higherVersionCode = infoJson.getLong("higherVersionCode");
long lowerVersionCode = infoJson.getLong("lowerVersionCode");
data.packages.add(new PackageRollbackInfo(
new VersionedPackage(packageName, higherVersionCode),
new VersionedPackage(packageName, lowerVersionCode)));
}
data.packages.addAll(packageRollbackInfosFromJson(dataJson.getJSONArray("packages")));
data.timestamp = Instant.parse(dataJson.getString("timestamp"));
return data;
} catch (JSONException | DateTimeParseException e) {
@@ -248,6 +219,40 @@ class RollbackStore {
}
}
private JSONObject toJson(PackageRollbackInfo info) throws JSONException {
JSONObject json = new JSONObject();
json.put("packageName", info.getPackageName());
json.put("higherVersionCode", info.getVersionRolledBackFrom().getLongVersionCode());
json.put("lowerVersionCode", info.getVersionRolledBackTo().getLongVersionCode());
return json;
}
private PackageRollbackInfo packageRollbackInfoFromJson(JSONObject json) throws JSONException {
String packageName = json.getString("packageName");
long higherVersionCode = json.getLong("higherVersionCode");
long lowerVersionCode = json.getLong("lowerVersionCode");
return new PackageRollbackInfo(
new VersionedPackage(packageName, higherVersionCode),
new VersionedPackage(packageName, lowerVersionCode));
}
private JSONArray toJson(List<PackageRollbackInfo> infos) throws JSONException {
JSONArray json = new JSONArray();
for (PackageRollbackInfo info : infos) {
json.put(toJson(info));
}
return json;
}
private List<PackageRollbackInfo> packageRollbackInfosFromJson(JSONArray json)
throws JSONException {
List<PackageRollbackInfo> infos = new ArrayList<>();
for (int i = 0; i < json.length(); ++i) {
infos.add(packageRollbackInfoFromJson(json.getJSONObject(i)));
}
return infos;
}
/**
* Deletes a file completely.
* If the file is a directory, its contents are deleted as well.

View File

@@ -29,7 +29,7 @@ import java.util.concurrent.TimeUnit;
/**
* A broadcast receiver that can be used to get
* ACTION_PACKAGE_ROLLBACK_EXECUTED broadcasts.
* ACTION_ROLLBACK_COMMITTED broadcasts.
*/
class RollbackBroadcastReceiver extends BroadcastReceiver {
@@ -43,7 +43,7 @@ class RollbackBroadcastReceiver extends BroadcastReceiver {
*/
RollbackBroadcastReceiver() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
filter.addAction(Intent.ACTION_ROLLBACK_COMMITTED);
InstrumentationRegistry.getContext().registerReceiver(this, filter);
}

View File

@@ -31,11 +31,8 @@ import android.support.test.InstrumentationRegistry;
import android.util.Log;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Ignore;
@@ -43,6 +40,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -86,8 +84,8 @@ public class RollbackTest {
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.MANAGE_ROLLBACKS);
// Register a broadcast receiver for notification when the rollback is
// done executing.
// Register a broadcast receiver for notification when the
// rollback has been committed.
RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver();
RollbackManager rm = RollbackTestUtils.getRollbackManager();
@@ -99,12 +97,11 @@ public class RollbackTest {
// uninstalled and when rollback manager deletes the rollback. Fix it
// so that's not the case!
for (int i = 0; i < 5; ++i) {
for (RollbackInfo info : rm.getRecentlyExecutedRollbacks()) {
if (TEST_APP_A.equals(info.targetPackage.getPackageName())) {
Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect.");
Thread.sleep(1000);
break;
}
RollbackInfo rollback = getUniqueRollbackInfoForPackage(
rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
if (rollback != null) {
Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect.");
Thread.sleep(1000);
}
}
@@ -113,13 +110,11 @@ public class RollbackTest {
// between when the app is uninstalled and when the previously
// available rollback, if any, is removed.
Thread.sleep(1000);
assertNull(rm.getAvailableRollback(TEST_APP_A));
assertFalse(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A));
// There should be no recently executed rollbacks for this package.
for (RollbackInfo info : rm.getRecentlyExecutedRollbacks()) {
assertNotEquals(TEST_APP_A, info.targetPackage.getPackageName());
}
// There should be no recently committed rollbacks for this package.
assertNull(getUniqueRollbackInfoForPackage(
rm.getRecentlyCommittedRollbacks(), TEST_APP_A));
// Install v1 of the app (without rollbacks enabled).
RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
@@ -134,10 +129,9 @@ public class RollbackTest {
// between when the app is installed and when the rollback
// is made available.
Thread.sleep(1000);
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollback);
assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
RollbackInfo rollback = getUniqueRollbackInfoForPackage(
rm.getAvailableRollbacks(), TEST_APP_A);
assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
// We should not have received any rollback requests yet.
// TODO: Possibly flaky if, by chance, some other app on device
@@ -156,15 +150,9 @@ public class RollbackTest {
assertNull(broadcastReceiver.poll(0, TimeUnit.SECONDS));
// Verify the recent rollback has been recorded.
rollback = null;
for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
assertNull(rollback);
rollback = r;
}
}
assertNotNull(rollback);
assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
rollback = getUniqueRollbackInfoForPackage(
rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
broadcastReceiver.unregister();
context.unregisterReceiver(enableRollbackReceiver);
@@ -202,28 +190,25 @@ public class RollbackTest {
// is made available.
Thread.sleep(1000);
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollbackA);
assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
rm.getAvailableRollbacks(), TEST_APP_A);
assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
assertNotNull(rollbackB);
assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
rm.getAvailableRollbacks(), TEST_APP_B);
assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
// Reload the persisted data.
rm.reloadPersistedData();
// The apps should still be available for rollback.
rollbackA = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollbackA);
assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
rollbackA = getUniqueRollbackInfoForPackage(
rm.getAvailableRollbacks(), TEST_APP_A);
assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
rollbackB = rm.getAvailableRollback(TEST_APP_B);
assertNotNull(rollbackB);
assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
rollbackB = getUniqueRollbackInfoForPackage(
rm.getAvailableRollbacks(), TEST_APP_B);
assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
// Rollback of B should not rollback A
RollbackTestUtils.rollback(rollbackB);
@@ -264,28 +249,23 @@ public class RollbackTest {
// is made available.
Thread.sleep(1000);
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollbackA);
assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
rm.getAvailableRollbacks(), TEST_APP_A);
assertRollbackInfoForAandB(rollbackA);
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
assertNotNull(rollbackB);
assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
rm.getAvailableRollbacks(), TEST_APP_B);
assertRollbackInfoForAandB(rollbackB);
// Reload the persisted data.
rm.reloadPersistedData();
// The apps should still be available for rollback.
rollbackA = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollbackA);
assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
rollbackA = getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A);
assertRollbackInfoForAandB(rollbackA);
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
rollbackB = rm.getAvailableRollback(TEST_APP_B);
assertNotNull(rollbackB);
assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
rollbackB = getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_B);
assertRollbackInfoForAandB(rollbackB);
// Rollback of B should rollback A as well
RollbackTestUtils.rollback(rollbackB);
@@ -297,10 +277,10 @@ public class RollbackTest {
}
/**
* Test that recently executed rollback data is properly persisted.
* Test that recently committed rollback data is properly persisted.
*/
@Test
public void testRecentlyExecutedRollbackPersistence() throws Exception {
public void testRecentlyCommittedRollbackPersistence() throws Exception {
try {
RollbackTestUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
@@ -319,37 +299,27 @@ public class RollbackTest {
// between when the app is installed and when the rollback
// is made available.
Thread.sleep(1000);
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
RollbackInfo rollback = getUniqueRollbackInfoForPackage(
rm.getAvailableRollbacks(), TEST_APP_A);
// Roll back the app.
RollbackTestUtils.rollback(rollback);
assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
// Verify the recent rollback has been recorded.
rollback = null;
for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
assertNull(rollback);
rollback = r;
}
}
rollback = getUniqueRollbackInfoForPackage(
rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
assertNotNull(rollback);
assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
// Reload the persisted data.
rm.reloadPersistedData();
// Verify the recent rollback is still recorded.
rollback = null;
for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
assertNull(rollback);
rollback = r;
}
}
rollback = getUniqueRollbackInfoForPackage(
rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
assertNotNull(rollback);
assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
} finally {
RollbackTestUtils.dropShellPermissionIdentity();
}
@@ -378,17 +348,16 @@ public class RollbackTest {
// between when the app is installed and when the rollback
// is made available.
Thread.sleep(1000);
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollback);
assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
RollbackInfo rollback = getUniqueRollbackInfoForPackage(
rm.getAvailableRollbacks(), TEST_APP_A);
assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
// Expire the rollback.
rm.expireRollbackForPackage(TEST_APP_A);
// The rollback should no longer be available.
assertNull(rm.getAvailableRollback(TEST_APP_A));
assertFalse(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
assertNull(getUniqueRollbackInfoForPackage(
rm.getAvailableRollbacks(), TEST_APP_A));
} finally {
RollbackTestUtils.dropShellPermissionIdentity();
}
@@ -459,7 +428,8 @@ public class RollbackTest {
processUserData(TEST_APP_A);
RollbackManager rm = RollbackTestUtils.getRollbackManager();
RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
RollbackInfo rollback = getUniqueRollbackInfoForPackage(
rm.getAvailableRollbacks(), TEST_APP_A);
RollbackTestUtils.rollback(rollback);
processUserData(TEST_APP_A);
} finally {
@@ -469,12 +439,12 @@ public class RollbackTest {
/**
* Test restrictions on rollback broadcast sender.
* A random app should not be able to send a PACKAGE_ROLLBACK_EXECUTED broadcast.
* A random app should not be able to send a ROLLBACK_COMMITTED broadcast.
*/
@Test
public void testRollbackBroadcastRestrictions() throws Exception {
RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver();
Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
try {
InstrumentationRegistry.getContext().sendBroadcast(broadcast);
fail("Succeeded in sending restricted broadcast from app context.");
@@ -516,18 +486,18 @@ public class RollbackTest {
assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
// Both test apps should now be available for rollback, and the
// targetPackage returned for rollback should be correct.
// RollbackInfo returned for the rollbacks should be correct.
// TODO: See if there is a way to remove this race condition
// between when the app is installed and when the rollback
// is made available.
Thread.sleep(1000);
RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollbackA);
assertEquals(TEST_APP_A, rollbackA.targetPackage.getPackageName());
RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
rm.getAvailableRollbacks(), TEST_APP_A);
assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
assertNotNull(rollbackB);
assertEquals(TEST_APP_B, rollbackB.targetPackage.getPackageName());
RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
rm.getAvailableRollbacks(), TEST_APP_B);
assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
// Executing rollback should roll back the correct package.
RollbackTestUtils.rollback(rollbackA);
@@ -558,21 +528,14 @@ public class RollbackTest {
RollbackManager rm = RollbackTestUtils.getRollbackManager();
try {
rm.getAvailableRollback(TEST_APP_A);
rm.getAvailableRollbacks();
fail("expected SecurityException");
} catch (SecurityException e) {
// Expected.
}
try {
rm.getPackagesWithAvailableRollbacks();
fail("expected SecurityException");
} catch (SecurityException e) {
// Expected.
}
try {
rm.getRecentlyExecutedRollbacks();
rm.getRecentlyCommittedRollbacks();
fail("expected SecurityException");
} catch (SecurityException e) {
// Expected.
@@ -581,7 +544,7 @@ public class RollbackTest {
try {
// TODO: What if the implementation checks arguments for non-null
// first? Then this test isn't valid.
rm.executeRollback(null, null);
rm.commitRollback(null, null);
fail("expected SecurityException");
} catch (SecurityException e) {
// Expected.
@@ -629,12 +592,9 @@ public class RollbackTest {
assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
// TEST_APP_A should now be available for rollback.
assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollback);
// TODO: Test the dependent apps for rollback are correct once we
// support that in the RollbackInfo API.
RollbackInfo rollback = getUniqueRollbackInfoForPackage(
rm.getAvailableRollbacks(), TEST_APP_A);
assertRollbackInfoForAandB(rollback);
// Rollback the app. It should cause both test apps to be rolled
// back.
@@ -642,14 +602,16 @@ public class RollbackTest {
assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
// We should not see a recent rollback listed for TEST_APP_B
for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
assertNotEquals(TEST_APP_B, r.targetPackage.getPackageName());
}
// We should see recent rollbacks listed for both A and B.
Thread.sleep(1000);
RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
// TODO: Test the listed dependent apps for the recently executed
// rollback are correct once we support that in the RollbackInfo
// API.
RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
rm.getRecentlyCommittedRollbacks(), TEST_APP_B);
assertRollbackInfoForAandB(rollbackB);
assertEquals(rollbackA.getRollbackId(), rollbackB.getRollbackId());
} finally {
RollbackTestUtils.dropShellPermissionIdentity();
}
@@ -697,13 +659,13 @@ public class RollbackTest {
// between when the app is installed and when the rollback
// is made available.
Thread.sleep(1000);
RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
assertNotNull(rollbackA);
assertEquals(TEST_APP_A, rollbackA.targetPackage.getPackageName());
RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
rm.getAvailableRollbacks(), TEST_APP_A);
assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
assertNotNull(rollbackB);
assertEquals(TEST_APP_B, rollbackB.targetPackage.getPackageName());
RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
rm.getAvailableRollbacks(), TEST_APP_B);
assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
// Start apps PackageWatchdog#TRIGGER_FAILURE_COUNT times so TEST_APP_A crashes
for (int i = 0; i < 5; i++) {
@@ -724,4 +686,47 @@ public class RollbackTest {
RollbackTestUtils.dropShellPermissionIdentity();
}
}
// Helper function to test the value of a RollbackInfo with single package
private void assertRollbackInfoEquals(String packageName,
long versionRolledBackFrom, long versionRolledBackTo,
RollbackInfo info) {
assertNotNull(info);
assertEquals(1, info.getPackages().size());
assertPackageRollbackInfoEquals(packageName, versionRolledBackFrom, versionRolledBackTo,
info.getPackages().get(0));
}
// Helper function to test that the given rollback info is a rollback for
// the atomic set {A2, B2} -> {A1, B1}.
private void assertRollbackInfoForAandB(RollbackInfo rollback) {
assertNotNull(rollback);
assertEquals(2, rollback.getPackages().size());
if (TEST_APP_A.equals(rollback.getPackages().get(0).getPackageName())) {
assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.getPackages().get(0));
assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollback.getPackages().get(1));
} else {
assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollback.getPackages().get(0));
assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.getPackages().get(1));
}
}
// Helper function to return the RollbackInfo with a given package in the
// list of rollbacks. Throws an assertion failure if there is more than
// one such rollback info. Returns null if there are no such rollback
// infos.
private RollbackInfo getUniqueRollbackInfoForPackage(List<RollbackInfo> rollbacks,
String packageName) {
RollbackInfo found = null;
for (RollbackInfo rollback : rollbacks) {
for (PackageRollbackInfo info : rollback.getPackages()) {
if (packageName.equals(info.getPackageName())) {
assertNull(found);
found = rollback;
break;
}
}
}
return found;
}
}

View File

@@ -90,12 +90,12 @@ class RollbackTestUtils {
}
/**
* Execute the given rollback.
* Commit the given rollback.
* @throws AssertionError if the rollback fails.
*/
static void rollback(RollbackInfo rollback) throws InterruptedException {
RollbackManager rm = getRollbackManager();
rm.executeRollback(rollback, LocalIntentSender.getIntentSender());
rm.commitRollback(rollback, LocalIntentSender.getIntentSender());
assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
}