Merge "Notify PackageHealthObservers of the package versionCode on package failure"

This commit is contained in:
Zimuzo Ezeozue
2019-01-30 10:13:41 +00:00
committed by Android (Google) Code Review
5 changed files with 90 additions and 34 deletions

View File

@@ -21,6 +21,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.VersionedPackage;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
@@ -230,7 +231,6 @@ public class PackageWatchdog {
return null;
}
// TODO(zezeozue:) Accept current versionCodes of failing packages?
/**
* Called when a process fails either due to a crash or ANR.
*
@@ -239,15 +239,16 @@ public class PackageWatchdog {
*
* <p>This method could be called frequently if there is a severe problem on the device.
*/
public void onPackageFailure(String[] packages) {
public void onPackageFailure(List<VersionedPackage> packages) {
mWorkerHandler.post(() -> {
synchronized (mLock) {
if (mAllObservers.isEmpty()) {
return;
}
for (int pIndex = 0; pIndex < packages.length; pIndex++) {
String packageToReport = packages[pIndex];
for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
String packageToReport = packages.get(pIndex).getPackageName();
long packageVersionCode = packages.get(pIndex).getVersionCode();
// Observer that will receive failure for packageToReport
PackageHealthObserver currentObserverToNotify = null;
int currentObserverImpact = Integer.MAX_VALUE;
@@ -258,7 +259,8 @@ public class PackageWatchdog {
PackageHealthObserver registeredObserver = observer.mRegisteredObserver;
if (registeredObserver != null
&& observer.onPackageFailure(packageToReport)) {
int impact = registeredObserver.onHealthCheckFailed(packageToReport);
int impact = registeredObserver.onHealthCheckFailed(packageToReport,
packageVersionCode);
if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
&& impact < currentObserverImpact) {
currentObserverToNotify = registeredObserver;
@@ -269,7 +271,7 @@ public class PackageWatchdog {
// Execute action with least user impact
if (currentObserverToNotify != null) {
currentObserverToNotify.execute(packageToReport);
currentObserverToNotify.execute(packageToReport, packageVersionCode);
}
}
}
@@ -313,14 +315,14 @@ public class PackageWatchdog {
* @return any one of {@link PackageHealthObserverImpact} to express the impact
* to the user on {@link #execute}
*/
@PackageHealthObserverImpact int onHealthCheckFailed(String packageName);
@PackageHealthObserverImpact int onHealthCheckFailed(String packageName, long versionCdoe);
/**
* Executes mitigation for {@link #onHealthCheckFailed}.
*
* @return {@code true} if action was executed successfully, {@code false} otherwise
*/
boolean execute(String packageName);
boolean execute(String packageName, long versionCode);
// TODO(zezeozue): Ensure uniqueness?
/**

View File

@@ -34,6 +34,7 @@ import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.VersionedPackage;
import android.net.Uri;
import android.os.Binder;
import android.os.Message;
@@ -60,6 +61,7 @@ import com.android.server.wm.WindowProcessController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.List;
/**
* Controls error conditions in applications.
@@ -411,7 +413,7 @@ class AppErrors {
} else {
// If a non-persistent app is stuck in crash loop, we want to inform
// the package watchdog, maybe an update or experiment can be rolled back.
mPackageWatchdog.onPackageFailure(r.getPackageList());
mPackageWatchdog.onPackageFailure(r.getPackageListWithVersionCode());
}
}
@@ -830,7 +832,7 @@ class AppErrors {
void handleShowAnrUi(Message msg) {
Dialog dialogToShow = null;
String[] packageList = null;
List<VersionedPackage> packageList = null;
synchronized (mService) {
AppNotRespondingDialog.Data data = (AppNotRespondingDialog.Data) msg.obj;
final ProcessRecord proc = data.proc;
@@ -839,7 +841,7 @@ class AppErrors {
return;
}
if (!proc.isPersistent()) {
packageList = proc.getPackageList();
packageList = proc.getPackageListWithVersionCode();
}
if (proc.anrDialog != null) {
Slog.e(TAG, "App already has anr dialog: " + proc);

View File

@@ -32,6 +32,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.VersionedPackage;
import android.content.res.CompatibilityInfo;
import android.os.Binder;
import android.os.Debug;
@@ -66,6 +67,7 @@ import java.io.File;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Full information about a particular process that
@@ -974,6 +976,18 @@ final class ProcessRecord implements WindowProcessListener {
return list;
}
public List<VersionedPackage> getPackageListWithVersionCode() {
int size = pkgList.size();
if (size == 0) {
return null;
}
List<VersionedPackage> list = new ArrayList<>();
for (int i = 0; i < pkgList.size(); i++) {
list.add(new VersionedPackage(pkgList.keyAt(i), pkgList.valueAt(i).appVersion));
}
return list;
}
WindowProcessController getWindowProcessController() {
return mWindowProcessController;
}

View File

@@ -54,8 +54,8 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
}
@Override
public int onHealthCheckFailed(String packageName) {
RollbackInfo rollback = getAvailableRollback(packageName);
public int onHealthCheckFailed(String packageName, long versionCode) {
RollbackInfo rollback = getAvailableRollback(packageName, versionCode);
if (rollback == null) {
// Don't handle the notification, no rollbacks available for the package
return PackageHealthObserverImpact.USER_IMPACT_NONE;
@@ -65,8 +65,8 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
}
@Override
public boolean execute(String packageName) {
RollbackInfo rollback = getAvailableRollback(packageName);
public boolean execute(String packageName, long versionCode) {
RollbackInfo rollback = getAvailableRollback(packageName, versionCode);
if (rollback == null) {
// Expected a rollback to be available, what happened?
return false;
@@ -110,11 +110,12 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
}
private RollbackInfo getAvailableRollback(String packageName) {
private RollbackInfo getAvailableRollback(String packageName, long versionCode) {
for (RollbackInfo rollback : mRollbackManager.getAvailableRollbacks()) {
for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
if (packageName.equals(packageRollback.getPackageName())) {
// TODO(zezeozue): Only rollback if rollback version == failed package version
if (packageName.equals(packageRollback.getPackageName())
&& packageRollback.getVersionRolledBackFrom().getVersionCode()
== versionCode) {
return rollback;
}
}

View File

@@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.content.pm.VersionedPackage;
import android.os.test.TestLooper;
import android.support.test.InstrumentationRegistry;
@@ -47,6 +48,7 @@ public class PackageWatchdogTest {
private static final String APP_B = "com.package.b";
private static final String APP_C = "com.package.c";
private static final String APP_D = "com.package.d";
private static final long VERSION_CODE = 1L;
private static final String OBSERVER_NAME_1 = "observer1";
private static final String OBSERVER_NAME_2 = "observer2";
private static final String OBSERVER_NAME_3 = "observer3";
@@ -193,7 +195,7 @@ public class PackageWatchdogTest {
// Then fail APP_A below the threshold
for (int i = 0; i < TRIGGER_FAILURE_COUNT - 1; i++) {
watchdog.onPackageFailure(new String[]{APP_A});
watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
}
// Run handler so package failures are dispatched to observers
@@ -209,12 +211,10 @@ public class PackageWatchdogTest {
* the failed packages.
*/
@Test
public void testPackageFailureNotifyNone() throws Exception {
public void testPackageFailureDifferentPackageNotifyNone() throws Exception {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_HIGH);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2,
PackageHealthObserverImpact.USER_IMPACT_HIGH);
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
@@ -222,7 +222,7 @@ public class PackageWatchdogTest {
// Then fail APP_C (not observed) above the threshold
for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
watchdog.onPackageFailure(new String[]{APP_C});
watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)));
}
// Run handler so package failures are dispatched to observers
@@ -233,6 +233,40 @@ public class PackageWatchdogTest {
assertEquals(0, observer2.mFailedPackages.size());
}
/**
* Test package failure and does not notify any observer because the failed package version
* does not match the available rollback-from-version.
*/
@Test
public void testPackageFailureDifferentVersionNotifyNone() throws Exception {
PackageWatchdog watchdog = createWatchdog();
long differentVersionCode = 2L;
TestObserver observer = new TestObserver(OBSERVER_NAME_1) {
public int onHealthCheckFailed(String packageName, long versionCode) {
if (versionCode == VERSION_CODE) {
// Only rollback for specific versionCode
return PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
}
return PackageHealthObserverImpact.USER_IMPACT_NONE;
}
};
watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A (different version) above the threshold
for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
watchdog.onPackageFailure(Arrays.asList(
new VersionedPackage(APP_A, differentVersionCode)));
}
// Run handler so package failures are dispatched to observers
mTestLooper.dispatchAll();
// Verify that observers are not notified
assertEquals(0, observer.mFailedPackages.size());
}
/**
* Test package failure and notifies only least impact observers.
*/
@@ -260,7 +294,10 @@ public class PackageWatchdogTest {
// Then fail all apps above the threshold
for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
watchdog.onPackageFailure(new String[]{APP_A, APP_B, APP_C, APP_D});
watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
new VersionedPackage(APP_B, VERSION_CODE),
new VersionedPackage(APP_C, VERSION_CODE),
new VersionedPackage(APP_D, VERSION_CODE)));
}
// Run handler so package failures are dispatched to observers
@@ -297,7 +334,7 @@ public class PackageWatchdogTest {
* <ul>
*/
@Test
public void testPackageFailureNotifyLeastSuccessively() throws Exception {
public void testPackageFailureNotifyLeastImpactSuccessively() throws Exception {
PackageWatchdog watchdog = createWatchdog();
TestObserver observerFirst = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_LOW);
@@ -310,7 +347,7 @@ public class PackageWatchdogTest {
// Then fail APP_A above the threshold
for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
watchdog.onPackageFailure(new String[]{APP_A});
watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
}
// Run handler so package failures are dispatched to observers
mTestLooper.dispatchAll();
@@ -327,7 +364,7 @@ public class PackageWatchdogTest {
// Then fail APP_A again above the threshold
for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
watchdog.onPackageFailure(new String[]{APP_A});
watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
}
// Run handler so package failures are dispatched to observers
mTestLooper.dispatchAll();
@@ -344,7 +381,7 @@ public class PackageWatchdogTest {
// Then fail APP_A again above the threshold
for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
watchdog.onPackageFailure(new String[]{APP_A});
watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
}
// Run handler so package failures are dispatched to observers
mTestLooper.dispatchAll();
@@ -361,7 +398,7 @@ public class PackageWatchdogTest {
// Then fail APP_A again above the threshold
for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
watchdog.onPackageFailure(new String[]{APP_A});
watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
}
// Run handler so package failures are dispatched to observers
mTestLooper.dispatchAll();
@@ -388,7 +425,7 @@ public class PackageWatchdogTest {
// Then fail APP_A above the threshold
for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
watchdog.onPackageFailure(new String[]{APP_A});
watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
}
// Run handler so package failures are dispatched to observers
@@ -420,11 +457,11 @@ public class PackageWatchdogTest {
mImpact = impact;
}
public int onHealthCheckFailed(String packageName) {
public int onHealthCheckFailed(String packageName, long versionCode) {
return mImpact;
}
public boolean execute(String packageName) {
public boolean execute(String packageName, long versionCode) {
mFailedPackages.add(packageName);
return true;
}