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

Commit 3cae8500 authored by Zimuzo Ezeozue's avatar Zimuzo Ezeozue Committed by Android (Google) Code Review
Browse files

Merge changes from topic "explicit"

* changes:
  Notify PacakgeWatchdog when apps are ready
  Add explicit health check to PackageWatchdog
parents 90cd3d6d 7b56e560
Loading
Loading
Loading
Loading
+133 −59
Original line number Diff line number Diff line
@@ -39,7 +39,9 @@ import android.text.TextUtils;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;

import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;

@@ -50,10 +52,22 @@ class ExplicitHealthCheckController {
    private static final String TAG = "ExplicitHealthCheckController";
    private final Object mLock = new Object();
    private final Context mContext;
    @GuardedBy("mLock") @Nullable private StateCallback mStateCallback;

    // Called everytime the service is connected, so the watchdog can sync it's state with
    // the health check service. In practice, should never be null after it has been #setEnabled.
    @GuardedBy("mLock") @Nullable private Runnable mOnConnected;
    // Called everytime a package passes the health check, so the watchdog is notified of the
    // passing check. In practice, should never be null after it has been #setEnabled.
    @GuardedBy("mLock") @Nullable private Consumer<String> mPassedConsumer;
    // Actual binder object to the explicit health check service.
    @GuardedBy("mLock") @Nullable private IExplicitHealthCheckService mRemoteService;
    @GuardedBy("mLock") @Nullable private ServiceConnection mConnection;
    // Cache for packages supporting explicit health checks. This cache should not change while
    // the health check service is running.
    @GuardedBy("mLock") @Nullable private List<String> mSupportedPackages;
    // Connection to the explicit health check service, necessary to unbind
    @GuardedBy("mLock") @Nullable private ServiceConnection mConnection;
    // Bind state of the explicit health check service.
    @GuardedBy("mLock") private boolean mEnabled;

    ExplicitHealthCheckController(Context context) {
        mContext = context;
@@ -61,28 +75,40 @@ class ExplicitHealthCheckController {

    /**
     * Requests an explicit health check for {@code packageName}.
     * After this request, the callback registered on {@link startService} can receive explicit
     * After this request, the callback registered on {@link #setCallbacks} can receive explicit
     * health check passed results.
     *
     * @throws IllegalStateException if the service is not started
     */
    public void request(String packageName) throws RemoteException {
        synchronized (mLock) {
            if (!mEnabled) {
                return;
            }

            enforceServiceReadyLocked();

            Slog.i(TAG, "Requesting health check for package " + packageName);
            mRemoteService.request(packageName);
        }
    }

    /**
     * Cancels all explicit health checks for {@code packageName}.
     * After this request, the callback registered on {@link startService} can no longer receive
     * After this request, the callback registered on {@link #setCallbacks} can no longer receive
     * explicit health check passed results.
     *
     * @throws IllegalStateException if the service is not started
     */
    public void cancel(String packageName) throws RemoteException {
        synchronized (mLock) {
            if (!mEnabled) {
                return;
            }

            enforceServiceReadyLocked();

            Slog.i(TAG, "Cancelling health check for package " + packageName);
            mRemoteService.cancel(packageName);
        }
    }
@@ -95,13 +121,21 @@ class ExplicitHealthCheckController {
     */
    public void getSupportedPackages(Consumer<List<String>> consumer) throws RemoteException {
        synchronized (mLock) {
            if (!mEnabled) {
                consumer.accept(Collections.emptyList());
                return;
            }

            enforceServiceReadyLocked();

            if (mSupportedPackages == null) {
                Slog.d(TAG, "Getting health check supported packages");
                mRemoteService.getSupportedPackages(new RemoteCallback(result -> {
                    mSupportedPackages = result.getStringArrayList(EXTRA_SUPPORTED_PACKAGES);
                    consumer.accept(mSupportedPackages);
                }));
            } else {
                Slog.d(TAG, "Getting cached health check supported packages");
                consumer.accept(mSupportedPackages);
            }
        }
@@ -115,95 +149,113 @@ class ExplicitHealthCheckController {
     */
    public void getRequestedPackages(Consumer<List<String>> consumer) throws RemoteException {
        synchronized (mLock) {
            if (!mEnabled) {
                consumer.accept(Collections.emptyList());
                return;
            }

            enforceServiceReadyLocked();

            Slog.d(TAG, "Getting health check requested packages");
            mRemoteService.getRequestedPackages(new RemoteCallback(
                    result -> consumer.accept(
                            result.getStringArrayList(EXTRA_REQUESTED_PACKAGES))));
        }
    }

    /** Enables or disables explicit health checks. */
    public void setEnabled(boolean enabled) {
        synchronized (mLock) {
            if (enabled == mEnabled) {
                return;
            }

            Slog.i(TAG, "Setting explicit health checks enabled " + enabled);
            mEnabled = enabled;
            if (enabled) {
                bindService();
            } else {
                unbindService();
            }
        }
    }

    /**
     * Starts the explicit health check service.
     *
     * @param stateCallback will receive important state changes changes
     * @param passedConsumer will accept packages that pass explicit health checks
     *
     * @throws IllegalStateException if the service is already started
     * Sets callbacks to listen to important events from the controller.
     * Should be called at initialization.
     */
    public void startService(StateCallback stateCallback, Consumer<String> passedConsumer) {
    public void setCallbacks(Runnable onConnected, Consumer<String> passedConsumer) {
        Preconditions.checkNotNull(onConnected);
        Preconditions.checkNotNull(passedConsumer);
        mOnConnected = onConnected;
        mPassedConsumer = passedConsumer;
    }

    /** Binds to the explicit health check service. */
    private void bindService() {
        synchronized (mLock) {
            if (mRemoteService != null) {
                throw new IllegalStateException("Explicit health check service already started.");
                return;
            }
            mStateCallback = stateCallback;
            ComponentName component = getServiceComponentNameLocked();
            if (component == null) {
                Slog.wtf(TAG, "Explicit health check service not found");
                return;
            }

            Intent intent = new Intent();
            intent.setComponent(component);
            // TODO: Fix potential race conditions during mConnection state transitions.
            // E.g after #onServiceDisconected, the mRemoteService object is invalid until
            // we get an #onServiceConnected.
            mConnection = new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    synchronized (mLock) {
                        mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service);
                        try {
                            mRemoteService.setCallback(new RemoteCallback(result -> {
                                String packageName =
                                        result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE);
                                if (!TextUtils.isEmpty(packageName)) {
                                    passedConsumer.accept(packageName);
                                } else {
                                    Slog.w(TAG, "Empty package passed explicit health check?");
                                }
                            }));
                            mStateCallback.onStart();
                    initState(service);
                    Slog.i(TAG, "Explicit health check service is connected " + name);
                        } catch (RemoteException e) {
                            Slog.wtf(TAG, "Coud not setCallback on explicit health check service");
                        }
                    }
                }

                @Override
                @MainThread
                public void onServiceDisconnected(ComponentName name) {
                    resetState();
                    // Service crashed or process was killed, #onServiceConnected will be called.
                    // Don't need to re-bind.
                    Slog.i(TAG, "Explicit health check service is disconnected " + name);
                }

                @Override
                public void onBindingDied(ComponentName name) {
                    resetState();
                    // Application hosting service probably got updated
                    // Need to re-bind.
                    synchronized (mLock) {
                        if (mEnabled) {
                            unbindService();
                            bindService();
                        }
                    }
                    Slog.i(TAG, "Explicit health check service binding is dead " + name);
                }

                @Override
                public void onNullBinding(ComponentName name) {
                    resetState();
                    Slog.i(TAG, "Explicit health check service binding is null " + name);
                    // Should never happen. Service returned null from #onBind.
                    Slog.wtf(TAG, "Explicit health check service binding is null?? " + name);
                }
            };

            ComponentName component = getServiceComponentNameLocked();
            if (component != null) {
                Intent intent = new Intent();
                intent.setComponent(component);
            Slog.i(TAG, "Binding to explicit health service");
            mContext.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE,
                    UserHandle.of(UserHandle.USER_SYSTEM));
        }
    }
    }

    // TODO: Differentiate between expected vs unexpected stop?
    /** Callback to receive important {@link ExplicitHealthCheckController} state changes. */
    abstract static class StateCallback {
        /** The controller is ready and we can request explicit health checks for packages */
        public void onStart() {}

        /** The controller is not ready and we cannot request explicit health checks for packages */
        public void onStop() {}
    }

    /** Stops the explicit health check service. */
    public void stopService() {
    /** Unbinds the explicit health check service. */
    private void unbindService() {
        synchronized (mLock) {
            if (mRemoteService != null) {
                Slog.i(TAG, "Unbinding from explicit health service");
                mContext.unbindService(mConnection);
                mRemoteService = null;
            }
        }
    }
@@ -247,19 +299,41 @@ class ExplicitHealthCheckController {
        return name;
    }

    private void resetState() {
    private void initState(IBinder service) {
        synchronized (mLock) {
            mStateCallback.onStop();
            mStateCallback = null;
            mSupportedPackages = null;
            mRemoteService = null;
            mConnection = null;
            mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service);
            try {
                mRemoteService.setCallback(new RemoteCallback(result -> {
                    String packageName = result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE);
                    if (!TextUtils.isEmpty(packageName)) {
                        synchronized (mLock) {
                            if (mPassedConsumer == null) {
                                Slog.w(TAG, "Health check passed for package " + packageName
                                        + "but no consumer registered.");
                            } else {
                                mPassedConsumer.accept(packageName);
                            }
                        }
                    } else {
                        Slog.w(TAG, "Empty package passed explicit health check?");
                    }
                }));
                if (mOnConnected == null) {
                    Slog.w(TAG, "Health check service connected but no runnable registered.");
                } else {
                    mOnConnected.run();
                }
            } catch (RemoteException e) {
                Slog.wtf(TAG, "Could not setCallback on explicit health check service");
            }
        }
    }

    @GuardedBy("mLock")
    private void enforceServiceReadyLocked() {
        if (mRemoteService == null) {
            // TODO: Try to bind to service
            throw new IllegalStateException("Explicit health check service not ready");
        }
    }
+223 −75

File changed.

Preview size limit exceeded, changes collapsed.

+2 −0
Original line number Diff line number Diff line
@@ -2135,6 +2135,8 @@ public class ActivityManagerService extends IActivityManager.Stub
                mService.mServices.systemServicesReady();
            } else if (phase == PHASE_ACTIVITY_MANAGER_READY) {
                mService.startBroadcastObservers();
            } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
                mService.mPackageWatchdog.onPackagesReady();
            }
        }
+1 −2
Original line number Diff line number Diff line
@@ -166,8 +166,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
     * 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,
                false /* withExplicitHealthCheck */);
        PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
    }

    /** Verifies the rollback state after a reboot. */
+206 −32

File changed.

Preview size limit exceeded, changes collapsed.