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

Commit e680d60f authored by Zimuzo's avatar Zimuzo
Browse files

Implement ExplicitHealthCheckService in ExtServices

If658567ca41bb0328e279735897fc50ab76abc11 added the
ExplicitHealthCheckService API to allow ExtServices to request explicit
health checks for packages and report to the PackageWatchdog.

We now implement this service and add a rudimentary NetworkChecker
that relies on listening to the ConnectivityManager for validated
network requests.

Bug: 120598832
Test: Manually tested that after an APEX update, the network stack
does not pass the explicit health check until WiFi is connected
successfully. If Wi-Fi is never connected and the network stack
monitoring duration is exceeded, the update is rolled back.

Change-Id: Ifa968b4c7107cabedf186dffbc5eb771a3840e7f
parent fdafee50
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@

    <uses-permission android:name="android.permission.MONITOR_DEFAULT_SMS_PACKAGE" />
    <uses-permission android:name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <uses-sdk
        android:targetSdkVersion="28"
@@ -78,6 +79,13 @@
            </intent-filter>
        </service>

        <service android:name=".watchdog.ExplicitHealthCheckServiceImpl"
                 android:permission="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE">
            <intent-filter>
                <action android:name="android.service.watchdog.ExplicitHealthCheckService" />
            </intent-filter>
        </service>

        <activity android:name=".notification.CopyCodeActivity"
                  android:exported="false"
                  android:theme="@android:style/Theme.NoDisplay"/>
+104 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.ext.services.watchdog;

import android.content.ComponentName;
import android.content.Intent;
import android.service.watchdog.ExplicitHealthCheckService;
import android.util.Log;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Routes explicit health check requests to the appropriate {@link ExplicitHealthChecker}.
 */
public final class ExplicitHealthCheckServiceImpl extends ExplicitHealthCheckService {
    private static final String TAG = "ExplicitHealthCheckServiceImpl";
    // TODO: Add build dependency on NetworkStack stable AIDL so we can stop hard coding class name
    private static final String NETWORK_STACK_CONNECTOR_CLASS =
            "android.net.INetworkStackConnector";
    // Modified only #onCreate, using concurrent collection to ensure thread visibility
    private final Map<String, ExplicitHealthChecker> mSupportedCheckers = new ConcurrentHashMap<>();

    @Override
    public void onCreate() {
        super.onCreate();
        initHealthCheckers();
    }

    @Override
    public void onRequestHealthCheck(String packageName) {
        ExplicitHealthChecker checker = mSupportedCheckers.get(packageName);
        if (checker != null) {
            checker.request();
        } else {
            Log.w(TAG, "Ignoring request for explicit health check for unsupported package "
                    + packageName);
        }
    }

    @Override
    public void onCancelHealthCheck(String packageName) {
        ExplicitHealthChecker checker = mSupportedCheckers.get(packageName);
        if (checker != null) {
            checker.cancel();
        } else {
            Log.w(TAG, "Ignoring request to cancel explicit health check for unsupported package "
                    + packageName);
        }
    }

    @Override
    public List<String> onGetSupportedPackages() {
        return new ArrayList<>(mSupportedCheckers.keySet());
    }

    @Override
    public List<String> onGetRequestedPackages() {
        List<String> packages = new ArrayList<>();
        Iterator<ExplicitHealthChecker> it = mSupportedCheckers.values().iterator();
        // Could potentially race, where we read a checker#isPending and it changes before we
        // return list. However, if it races and it is in the list, the caller might call #cancel
        // which would fail, but that is fine. If it races and it ends up *not* in the list, it was
        // already cancelled, so there's no need for the caller to cancel it
        while (it.hasNext()) {
            ExplicitHealthChecker checker = it.next();
            if (checker.isPending()) {
                packages.add(checker.getPackageName());
            }
        }
        return packages;
    }

    private void initHealthCheckers() {
        Intent intent = new Intent(NETWORK_STACK_CONNECTOR_CLASS);
        ComponentName comp = intent.resolveSystemService(getPackageManager(), 0);
        if (comp != null) {
            String networkStackPackageName = comp.getPackageName();
            mSupportedCheckers.put(networkStackPackageName,
                    new NetworkChecker(this, networkStackPackageName));
        } else {
            // On Go devices, or any device that does not ship the network stack module.
            // The network stack will live in system_server process, so no need to monitor.
            Log.i(TAG, "Network stack module not found");
        }
    }
}
+44 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.ext.services.watchdog;

/**
 * A type of explicit health check that can be performed on a device, e.g network health check
 */
interface ExplicitHealthChecker {
    /**
     * Requests a checker to listen to explicit health checks for {@link #getPackageName}.
     *  {@link #isPending} will now return {@code true}.
     */
    void request();

    /**
     * Cancels a pending explicit health check request for {@link #getPackageName}.
     * {@link #isPending} will now return {@code false}.
     */
    void cancel();

    /**
     * Returns {@code true} if a request is pending, {@code false} otherwise.
     */
    boolean isPending();

    /**
     * Returns the package name this checker can make requests for.
     */
    String getPackageName();
}
+92 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.ext.services.watchdog;

import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.service.watchdog.ExplicitHealthCheckService;

import com.android.internal.annotations.GuardedBy;

/**
 * Observes the network stack via the ConnectivityManager.
 */
final class NetworkChecker extends ConnectivityManager.NetworkCallback
        implements ExplicitHealthChecker {
    private static final String TAG = "NetworkChecker";

    private final Object mLock = new Object();
    private final ExplicitHealthCheckService mService;
    private final String mPackageName;
    @GuardedBy("mLock")
    private boolean mIsPending;

    NetworkChecker(ExplicitHealthCheckService service, String packageName) {
        mService = service;
        mPackageName = packageName;
    }

    @Override
    public void request() {
        synchronized (mLock) {
            if (mIsPending) {
                return;
            }
            mService.getSystemService(ConnectivityManager.class).registerNetworkCallback(
                    new NetworkRequest.Builder().build(), this);
            mIsPending = true;
        }
    }

    @Override
    public void cancel() {
        synchronized (mLock) {
            if (!mIsPending) {
                return;
            }
            mService.getSystemService(ConnectivityManager.class).unregisterNetworkCallback(this);
            mIsPending = false;
        }
    }

    @Override
    public boolean isPending() {
        synchronized (mLock) {
            return mIsPending;
        }
    }

    @Override
    public String getPackageName() {
        return mPackageName;
    }

    // TODO(b/120598832): Also monitor NetworkCallback#onAvailable to see if we have any
    // available networks that may be unusable. This could be additional signal to our heuristics
    @Override
    public void onCapabilitiesChanged(Network network, NetworkCapabilities capabilities) {
        synchronized (mLock) {
            if (mIsPending
                    && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
                mService.notifyHealthCheckPassed(mPackageName);
                cancel();
            }
        }
    }
}