Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 972e1cdb authored by Zimuzo's avatar Zimuzo
Browse files

Notify PackageHealthObservers of the package versionCode on package failure

PackageHealthObservers may need to verify that the package failure
notification they receive matches the expected package version code.
We now pass the version code along with the package name when notifying
observers.

Test: atest com.android.server.PackageWatchdogTest
Bug: 120598832
Change-Id: I272965d08a07240f3bde358039b52187ff2dd3cf
parent 6296773c
Loading
Loading
Loading
Loading
+10 −8
Original line number Diff line number Diff line
@@ -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?
        /**
+5 −3
Original line number Diff line number Diff line
@@ -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);
+14 −0
Original line number Diff line number Diff line
@@ -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
@@ -972,6 +974,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;
    }
+8 −7
Original line number Diff line number Diff line
@@ -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;
                }
            }
+53 −16
Original line number Diff line number Diff line
@@ -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;
        }