blob: dc9c8580824054db47bab32009a2c94cb32975ce [file] [log] [blame]
/*
* 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.service.watchdog;
import static android.os.Parcelable.Creator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* A service to provide packages supporting explicit health checks and route checks to these
* packages on behalf of the package watchdog.
*
* <p>To extend this class, you must declare the service in your manifest file with the
* {@link android.Manifest.permission#BIND_EXPLICIT_HEALTH_CHECK_SERVICE} permission,
* and include an intent filter with the {@link #SERVICE_INTERFACE} action. In adddition,
* your implementation must live in {@link PackageManger#SYSTEM_SHARED_LIBRARY_SERVICES}.
* For example:</p>
* <pre>
* &lt;service android:name=".FooExplicitHealthCheckService"
* android:exported="true"
* android:priority="100"
* android:permission="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"&gt;
* &lt;intent-filter&gt;
* &lt;action android:name="android.service.watchdog.ExplicitHealthCheckService" /&gt;
* &lt;/intent-filter&gt;
* &lt;/service&gt;
* </pre>
* @hide
*/
@SystemApi
public abstract class ExplicitHealthCheckService extends Service {
private static final String TAG = "ExplicitHealthCheckService";
/**
* {@link Bundle} key for a {@link List} of {@link PackageConfig} value.
*
* {@hide}
*/
public static final String EXTRA_SUPPORTED_PACKAGES =
"android.service.watchdog.extra.supported_packages";
/**
* {@link Bundle} key for a {@link List} of {@link String} value.
*
* {@hide}
*/
public static final String EXTRA_REQUESTED_PACKAGES =
"android.service.watchdog.extra.requested_packages";
/**
* {@link Bundle} key for a {@link String} value.
*
* {@hide}
*/
public static final String EXTRA_HEALTH_CHECK_PASSED_PACKAGE =
"android.service.watchdog.extra.health_check_passed_package";
/**
* The Intent action that a service must respond to. Add it to the intent filter of the service
* in its manifest.
*/
@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE =
"android.service.watchdog.ExplicitHealthCheckService";
/**
* The permission that a service must require to ensure that only Android system can bind to it.
* If this permission is not enforced in the AndroidManifest of the service, the system will
* skip that service.
*/
public static final String BIND_PERMISSION =
"android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE";
private final ExplicitHealthCheckServiceWrapper mWrapper =
new ExplicitHealthCheckServiceWrapper();
/**
* Called when the system requests an explicit health check for {@code packageName}.
*
* <p> When {@code packageName} passes the check, implementors should call
* {@link #notifyHealthCheckPassed} to inform the system.
*
* <p> It could take many hours before a {@code packageName} passes a check and implementors
* should never drop requests unless {@link onCancel} is called or the service dies.
*
* <p> Requests should not be queued and additional calls while expecting a result for
* {@code packageName} should have no effect.
*/
public abstract void onRequestHealthCheck(@NonNull String packageName);
/**
* Called when the system cancels the explicit health check request for {@code packageName}.
* Should do nothing if there are is no active request for {@code packageName}.
*/
public abstract void onCancelHealthCheck(@NonNull String packageName);
/**
* Called when the system requests for all the packages supporting explicit health checks. The
* system may request an explicit health check for any of these packages with
* {@link #onRequestHealthCheck}.
*
* @return all packages supporting explicit health checks
*/
@NonNull public abstract List<PackageConfig> onGetSupportedPackages();
/**
* Called when the system requests for all the packages that it has currently requested
* an explicit health check for.
*
* @return all packages expecting an explicit health check result
*/
@NonNull public abstract List<String> onGetRequestedPackages();
private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
@Nullable private RemoteCallback mCallback;
@Override
@NonNull
public final IBinder onBind(@NonNull Intent intent) {
return mWrapper;
}
/**
* Implementors should call this to notify the system when explicit health check passes
* for {@code packageName};
*/
public final void notifyHealthCheckPassed(@NonNull String packageName) {
mHandler.post(() -> {
if (mCallback != null) {
Objects.requireNonNull(packageName,
"Package passing explicit health check must be non-null");
Bundle bundle = new Bundle();
bundle.putString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE, packageName);
mCallback.sendResult(bundle);
} else {
Log.wtf(TAG, "System missed explicit health check result for " + packageName);
}
});
}
/**
* A PackageConfig contains a package supporting explicit health checks and the
* timeout in {@link System#uptimeMillis} across reboots after which health
* check requests from clients are failed.
*
* @hide
*/
@SystemApi
public static final class PackageConfig implements Parcelable {
private static final long DEFAULT_HEALTH_CHECK_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(1);
private final String mPackageName;
private final long mHealthCheckTimeoutMillis;
/**
* Creates a new instance.
*
* @param packageName the package name
* @param durationMillis the duration in milliseconds, must be greater than or
* equal to 0. If it is 0, it will use a system default value.
*/
public PackageConfig(@NonNull String packageName, long healthCheckTimeoutMillis) {
mPackageName = Preconditions.checkNotNull(packageName);
if (healthCheckTimeoutMillis == 0) {
mHealthCheckTimeoutMillis = DEFAULT_HEALTH_CHECK_TIMEOUT_MILLIS;
} else {
mHealthCheckTimeoutMillis = Preconditions.checkArgumentNonnegative(
healthCheckTimeoutMillis);
}
}
private PackageConfig(Parcel parcel) {
mPackageName = parcel.readString();
mHealthCheckTimeoutMillis = parcel.readLong();
}
/**
* Gets the package name.
*
* @return the package name
*/
public @NonNull String getPackageName() {
return mPackageName;
}
/**
* Gets the timeout in milliseconds to evaluate an explicit health check result after a
* request.
*
* @return the duration in {@link System#uptimeMillis} across reboots
*/
public long getHealthCheckTimeoutMillis() {
return mHealthCheckTimeoutMillis;
}
@Override
public String toString() {
return "PackageConfig{" + mPackageName + ", " + mHealthCheckTimeoutMillis + "}";
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof PackageConfig)) {
return false;
}
PackageConfig otherInfo = (PackageConfig) other;
return Objects.equals(otherInfo.getHealthCheckTimeoutMillis(),
mHealthCheckTimeoutMillis)
&& Objects.equals(otherInfo.getPackageName(), mPackageName);
}
@Override
public int hashCode() {
return Objects.hash(mPackageName, mHealthCheckTimeoutMillis);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeString(mPackageName);
parcel.writeLong(mHealthCheckTimeoutMillis);
}
public static final @NonNull Creator<PackageConfig> CREATOR = new Creator<PackageConfig>() {
@Override
public PackageConfig createFromParcel(Parcel source) {
return new PackageConfig(source);
}
@Override
public PackageConfig[] newArray(int size) {
return new PackageConfig[size];
}
};
}
private class ExplicitHealthCheckServiceWrapper extends IExplicitHealthCheckService.Stub {
@Override
public void setCallback(RemoteCallback callback) throws RemoteException {
mHandler.post(() -> {
mCallback = callback;
});
}
@Override
public void request(String packageName) throws RemoteException {
mHandler.post(() -> ExplicitHealthCheckService.this.onRequestHealthCheck(packageName));
}
@Override
public void cancel(String packageName) throws RemoteException {
mHandler.post(() -> ExplicitHealthCheckService.this.onCancelHealthCheck(packageName));
}
@Override
public void getSupportedPackages(RemoteCallback callback) throws RemoteException {
mHandler.post(() -> {
List<PackageConfig> packages =
ExplicitHealthCheckService.this.onGetSupportedPackages();
Objects.requireNonNull(packages, "Supported package list must be non-null");
Bundle bundle = new Bundle();
bundle.putParcelableArrayList(EXTRA_SUPPORTED_PACKAGES, new ArrayList<>(packages));
callback.sendResult(bundle);
});
}
@Override
public void getRequestedPackages(RemoteCallback callback) throws RemoteException {
mHandler.post(() -> {
List<String> packages =
ExplicitHealthCheckService.this.onGetRequestedPackages();
Objects.requireNonNull(packages, "Requested package list must be non-null");
Bundle bundle = new Bundle();
bundle.putStringArrayList(EXTRA_REQUESTED_PACKAGES, new ArrayList<>(packages));
callback.sendResult(bundle);
});
}
}
}