/* * 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.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.Service; import android.content.Intent; import android.content.pm.PackageManager; 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. * *

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 PackageManager#getServicesSystemSharedLibraryPackageName()}. * For example:

*
 *     <service android:name=".FooExplicitHealthCheckService"
 *             android:exported="true"
 *             android:priority="100"
 *             android:permission="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE">
 *         <intent-filter>
 *             <action android:name="android.service.watchdog.ExplicitHealthCheckService" />
 *         </intent-filter>
 *     </service>
 * 
* @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}. * *

When {@code packageName} passes the check, implementors should call * {@link #notifyHealthCheckPassed} to inform the system. * *

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. * *

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 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 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; } /** * Sets {@link RemoteCallback}, for testing purpose. * * @hide */ @TestApi public void setCallback(@Nullable RemoteCallback callback) { mCallback = callback; } /** * 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; } @NonNull @Override public String toString() { return "PackageConfig{" + mPackageName + ", " + mHealthCheckTimeoutMillis + "}"; } @Override public boolean equals(@Nullable 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(@SuppressLint({"MissingNullability"}) Parcel parcel, int flags) { parcel.writeString(mPackageName); parcel.writeLong(mHealthCheckTimeoutMillis); } public static final @NonNull Creator CREATOR = new Creator() { @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 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 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); }); } } }