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

Commit 91c145ac authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add PackageHealthObserverImpact"

parents 4086088a e5009cd8
Loading
Loading
Loading
Loading
+77 −45
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package com.android.server;

import static java.lang.annotation.RetentionPolicy.SOURCE;

import android.annotation.IntDef;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Environment;
@@ -46,6 +49,7 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Retention;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
@@ -55,7 +59,8 @@ import java.util.Set;

/**
 * Monitors the health of packages on the system and notifies interested observers when packages
 * fail. All registered observers will be notified until an observer takes a mitigation action.
 * fail. On failure, the registered observer with the least user impacting mitigation will
 * be notified.
 */
public class PackageWatchdog {
    private static final String TAG = "PackageWatchdog";
@@ -78,7 +83,8 @@ public class PackageWatchdog {
    private final Context mContext;
    // Handler to run package cleanup runnables
    private final Handler mTimerHandler;
    private final Handler mIoHandler;
    // Handler for processing IO and observer actions
    private final Handler mWorkerHandler;
    // Contains (observer-name -> observer-handle) that have ever been registered from
    // previous boots. Observers with all packages expired are periodically pruned.
    // It is saved to disk on system shutdown and repouplated on startup so it survives reboots.
@@ -101,7 +107,7 @@ public class PackageWatchdog {
        mPolicyFile = new AtomicFile(new File(new File(Environment.getDataDirectory(), "system"),
                        "package-watchdog.xml"));
        mTimerHandler = new Handler(Looper.myLooper());
        mIoHandler = BackgroundThread.getHandler();
        mWorkerHandler = BackgroundThread.getHandler();
        mPackageCleanup = this::rescheduleCleanup;
        loadFromFile();
    }
@@ -115,7 +121,7 @@ public class PackageWatchdog {
        mContext = context;
        mPolicyFile = new AtomicFile(new File(context.getFilesDir(), "package-watchdog.xml"));
        mTimerHandler = new Handler(looper);
        mIoHandler = mTimerHandler;
        mWorkerHandler = mTimerHandler;
        mPackageCleanup = this::rescheduleCleanup;
        loadFromFile();
    }
@@ -228,49 +234,46 @@ public class PackageWatchdog {
    /**
     * Called when a process fails either due to a crash or ANR.
     *
     * <p>All registered observers for the packages contained in the process will be notified in
     * order of priority until an observer signifies that it has taken action and other observers
     * should not notified.
     * <p>For each package contained in the process, one registered observer with the least user
     * impact will be notified for mitigation.
     *
     * <p>This method could be called frequently if there is a severe problem on the device.
     */
    public void onPackageFailure(String[] packages) {
        ArrayMap<String, List<PackageHealthObserver>> packagesToReport = new ArrayMap<>();
        mWorkerHandler.post(() -> {
            synchronized (mLock) {
                if (mAllObservers.isEmpty()) {
                    return;
                }

                for (int pIndex = 0; pIndex < packages.length; pIndex++) {
                // Observers interested in receiving packageName failures
                List<PackageHealthObserver> observersToNotify = new ArrayList<>();
                    String packageToReport = packages[pIndex];
                    // Observer that will receive failure for packageToReport
                    PackageHealthObserver currentObserverToNotify = null;
                    int currentObserverImpact = Integer.MAX_VALUE;

                    // Find observer with least user impact
                    for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
                    PackageHealthObserver registeredObserver =
                            mAllObservers.valueAt(oIndex).mRegisteredObserver;
                    if (registeredObserver != null) {
                        observersToNotify.add(registeredObserver);
                    }
                }
                // Save interested observers and notify them outside the lock
                if (!observersToNotify.isEmpty()) {
                    packagesToReport.put(packages[pIndex], observersToNotify);
                        ObserverInternal observer = mAllObservers.valueAt(oIndex);
                        PackageHealthObserver registeredObserver = observer.mRegisteredObserver;
                        if (registeredObserver != null
                                && observer.onPackageFailure(packageToReport)) {
                            int impact = registeredObserver.onHealthCheckFailed(packageToReport);
                            if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
                                    && impact < currentObserverImpact) {
                                currentObserverToNotify = registeredObserver;
                                currentObserverImpact = impact;
                            }
                        }
                    }

        // Notify observers
        for (int pIndex = 0; pIndex < packagesToReport.size(); pIndex++) {
            List<PackageHealthObserver> observers = packagesToReport.valueAt(pIndex);
            String packageName = packages[pIndex];
            for (int oIndex = 0; oIndex < observers.size(); oIndex++) {
                PackageHealthObserver observer = observers.get(oIndex);
                if (mAllObservers.get(observer.getName()).onPackageFailure(packageName)
                        && observer.onHealthCheckFailed(packageName)) {
                    // Observer has handled, do not notify others
                    break;
                    // Execute action with least user impact
                    if (currentObserverToNotify != null) {
                        currentObserverToNotify.execute(packageToReport);
                    }
                }
            }
        });
    }

    // TODO(zezeozue): Optimize write? Maybe only write a separate smaller file?
@@ -278,21 +281,46 @@ public class PackageWatchdog {
    /** Writes the package information to file during shutdown. */
    public void writeNow() {
        if (!mAllObservers.isEmpty()) {
            mIoHandler.removeCallbacks(this::saveToFile);
            mWorkerHandler.removeCallbacks(this::saveToFile);
            pruneObservers(SystemClock.uptimeMillis() - mUptimeAtLastRescheduleMs);
            saveToFile();
            Slog.i(TAG, "Last write to update package durations");
        }
    }

    /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
    @Retention(SOURCE)
    @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_NONE,
                     PackageHealthObserverImpact.USER_IMPACT_LOW,
                     PackageHealthObserverImpact.USER_IMPACT_MEDIUM,
                     PackageHealthObserverImpact.USER_IMPACT_HIGH})
    public @interface PackageHealthObserverImpact {
        /** No action to take. */
        int USER_IMPACT_NONE = 0;
        /* Action has low user impact, user of a device will barely notice. */
        int USER_IMPACT_LOW = 1;
        /* Action has medium user impact, user of a device will likely notice. */
        int USER_IMPACT_MEDIUM = 3;
        /* Action has high user impact, a last resort, user of a device will be very frustrated. */
        int USER_IMPACT_HIGH = 5;
    }

    /** Register instances of this interface to receive notifications on package failure. */
    public interface PackageHealthObserver {
        /**
         * Called when health check fails for the {@code packageName}.
         * @return {@code true} if action was taken and other observers should not be notified of
         * this failure, {@code false} otherwise.
         *
         * @return any one of {@link PackageHealthObserverImpact} to express the impact
         * to the user on {@link #execute}
         */
        @PackageHealthObserverImpact int onHealthCheckFailed(String packageName);

        /**
         * Executes mitigation for {@link #onHealthCheckFailed}.
         *
         * @return {@code true} if action was executed successfully, {@code false} otherwise
         */
        boolean onHealthCheckFailed(String packageName);
        boolean execute(String packageName);

        // TODO(zezeozue): Ensure uniqueness?
        /**
@@ -442,8 +470,8 @@ public class PackageWatchdog {
    }

    private void saveToFileAsync() {
        mIoHandler.removeCallbacks(this::saveToFile);
        mIoHandler.post(this::saveToFile);
        mWorkerHandler.removeCallbacks(this::saveToFile);
        mWorkerHandler.post(this::saveToFile);
    }

    /**
@@ -606,7 +634,11 @@ public class PackageWatchdog {
            } else {
                mFailures++;
            }
            return mFailures >= TRIGGER_FAILURE_COUNT;
            boolean failed = mFailures >= TRIGGER_FAILURE_COUNT;
            if (failed) {
                mFailures = 0;
            }
            return failed;
        }
    }
}
+52 −32
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.os.HandlerThread;

import com.android.server.PackageWatchdog;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;

import java.util.List;

@@ -39,10 +40,12 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
    private static final String TAG = "RollbackPackageHealthObserver";
    private static final String NAME = "rollback-observer";
    private Context mContext;
    private RollbackManager mRollbackManager;
    private Handler mHandler;

    RollbackPackageHealthObserver(Context context) {
        mContext = context;
        mRollbackManager = mContext.getSystemService(RollbackManager.class);
        HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver");
        handlerThread.start();
        mHandler = handlerThread.getThreadHandler();
@@ -50,33 +53,26 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
    }

    @Override
    public boolean onHealthCheckFailed(String packageName) {
        RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
        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;
                }
    public int onHealthCheckFailed(String packageName) {
        RollbackInfo rollback = getAvailableRollback(packageName);
        if (rollback == null) {
            // Don't handle the notification, no rollbacks available for the package
            return PackageHealthObserverImpact.USER_IMPACT_NONE;
        }
        }
        // Don't handle the notification, no rollbacks available
        return false;
        // Rollback is available, we may get a callback into #execute
        return PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
    }

    /**
     * Start observing health of {@code packages} for {@code durationMs}.
     * This may cause {@code packages} to be rolled back if they crash too freqeuntly.
     */
    public void startObservingHealth(List<String> packages, long durationMs) {
        PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
    @Override
    public boolean execute(String packageName) {
        RollbackInfo rollback = getAvailableRollback(packageName);
        if (rollback == null) {
            // Expected a rollback to be available, what happened?
            return false;
        }

    private void executeRollback(RollbackManager manager, RollbackInfo rollback) {
        // TODO(zezeozue): Log initiated metrics
        // TODO(zezeozue): Only rollback if rollback version == failed package version
        LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver((Intent result) -> {
            mHandler.post(() -> {
            int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
                    PackageInstaller.STATUS_FAILURE);
            if (status == PackageInstaller.STATUS_SUCCESS) {
@@ -87,12 +83,36 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
                // Rollback failed other observers should have a shot
            }
        });
        });
        manager.commitRollback(rollback, rollbackReceiver.getIntentSender());

        // TODO(zezeozue): Log initiated metrics
        mHandler.post(() ->
                mRollbackManager.commitRollback(rollback, rollbackReceiver.getIntentSender()));
        // Assume rollback executed successfully
        return true;
    }

    @Override
    public String getName() {
        return NAME;
    }

    /**
     * Start observing health of {@code packages} for {@code durationMs}.
     * This may cause {@code packages} to be rolled back if they crash too freqeuntly.
     */
    public void startObservingHealth(List<String> packages, long durationMs) {
        PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
    }

    private RollbackInfo getAvailableRollback(String packageName) {
        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
                    return rollback;
                }
            }
        }
        return null;
    }
}
+183 −33

File changed.

Preview size limit exceeded, changes collapsed.