• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.service.watchdog;
18 
19 import static android.os.Parcelable.Creator;
20 
21 import android.annotation.CallbackExecutor;
22 import android.annotation.FlaggedApi;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.SdkConstant;
26 import android.annotation.SuppressLint;
27 import android.annotation.SystemApi;
28 import android.app.Service;
29 import android.content.Intent;
30 import android.content.pm.PackageManager;
31 import android.crashrecovery.flags.Flags;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.os.IBinder;
35 import android.os.Looper;
36 import android.os.Parcel;
37 import android.os.Parcelable;
38 import android.os.RemoteCallback;
39 import android.os.RemoteException;
40 import android.util.Log;
41 
42 import com.android.internal.util.Preconditions;
43 
44 import java.util.ArrayList;
45 import java.util.List;
46 import java.util.Objects;
47 import java.util.concurrent.Executor;
48 import java.util.concurrent.TimeUnit;
49 import java.util.function.Consumer;
50 
51 /**
52  * A service to provide packages supporting explicit health checks and route checks to these
53  * packages on behalf of the package watchdog.
54  *
55  * <p>To extend this class, you must declare the service in your manifest file with the
56  * {@link android.Manifest.permission#BIND_EXPLICIT_HEALTH_CHECK_SERVICE} permission,
57  * and include an intent filter with the {@link #SERVICE_INTERFACE} action. In adddition,
58  * your implementation must live in
59  * {@link PackageManager#getServicesSystemSharedLibraryPackageName()}.
60  * For example:</p>
61  * <pre>
62  *     &lt;service android:name=".FooExplicitHealthCheckService"
63  *             android:exported="true"
64  *             android:priority="100"
65  *             android:permission="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"&gt;
66  *         &lt;intent-filter&gt;
67  *             &lt;action android:name="android.service.watchdog.ExplicitHealthCheckService" /&gt;
68  *         &lt;/intent-filter&gt;
69  *     &lt;/service&gt;
70  * </pre>
71  * @hide
72  */
73 @SystemApi
74 public abstract class ExplicitHealthCheckService extends Service {
75 
76     private static final String TAG = "ExplicitHealthCheckService";
77 
78     /**
79      * {@link Bundle} key for a {@link List} of {@link PackageConfig} value.
80      *
81      * {@hide}
82      */
83     public static final String EXTRA_SUPPORTED_PACKAGES =
84             "android.service.watchdog.extra.supported_packages";
85 
86     /**
87      * {@link Bundle} key for a {@link List} of {@link String} value.
88      *
89      * {@hide}
90      */
91     public static final String EXTRA_REQUESTED_PACKAGES =
92             "android.service.watchdog.extra.requested_packages";
93 
94     /**
95      * {@link Bundle} key for a {@link String} value.
96      */
97     @FlaggedApi(Flags.FLAG_ENABLE_CRASHRECOVERY)
98     public static final String EXTRA_HEALTH_CHECK_PASSED_PACKAGE =
99             "android.service.watchdog.extra.HEALTH_CHECK_PASSED_PACKAGE";
100 
101     /**
102      * The Intent action that a service must respond to. Add it to the intent filter of the service
103      * in its manifest.
104      */
105     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
106     public static final String SERVICE_INTERFACE =
107             "android.service.watchdog.ExplicitHealthCheckService";
108 
109     /**
110      * The permission that a service must require to ensure that only Android system can bind to it.
111      * If this permission is not enforced in the AndroidManifest of the service, the system will
112      * skip that service.
113      */
114     public static final String BIND_PERMISSION =
115             "android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE";
116 
117     private final ExplicitHealthCheckServiceWrapper mWrapper =
118             new ExplicitHealthCheckServiceWrapper();
119 
120     /**
121      * Called when the system requests an explicit health check for {@code packageName}.
122      *
123      * <p> When {@code packageName} passes the check, implementors should call
124      * {@link #notifyHealthCheckPassed} to inform the system.
125      *
126      * <p> It could take many hours before a {@code packageName} passes a check and implementors
127      * should never drop requests unless {@link onCancel} is called or the service dies.
128      *
129      * <p> Requests should not be queued and additional calls while expecting a result for
130      * {@code packageName} should have no effect.
131      */
onRequestHealthCheck(@onNull String packageName)132     public abstract void onRequestHealthCheck(@NonNull String packageName);
133 
134     /**
135      * Called when the system cancels the explicit health check request for {@code packageName}.
136      * Should do nothing if there are is no active request for {@code packageName}.
137      */
onCancelHealthCheck(@onNull String packageName)138     public abstract void onCancelHealthCheck(@NonNull String packageName);
139 
140     /**
141      * Called when the system requests for all the packages supporting explicit health checks. The
142      * system may request an explicit health check for any of these packages with
143      * {@link #onRequestHealthCheck}.
144      *
145      * @return all packages supporting explicit health checks
146      */
onGetSupportedPackages()147     @NonNull public abstract List<PackageConfig> onGetSupportedPackages();
148 
149     /**
150      * Called when the system requests for all the packages that it has currently requested
151      * an explicit health check for.
152      *
153      * @return all packages expecting an explicit health check result
154      */
onGetRequestedPackages()155     @NonNull public abstract List<String> onGetRequestedPackages();
156 
157     private final Handler mHandler = Handler.createAsync(Looper.getMainLooper());
158     @Nullable private Consumer<Bundle> mHealthCheckResultCallback;
159     @Nullable private Executor mCallbackExecutor;
160 
161     @Override
162     @NonNull
onBind(@onNull Intent intent)163     public final IBinder onBind(@NonNull Intent intent) {
164         return mWrapper;
165     }
166 
167     /**
168      * Sets a callback to be invoked when an explicit health check passes for a package.
169      * <p>
170      * The callback will receive a {@link Bundle} containing the package name that passed the
171      * health check, identified by the key {@link #EXTRA_HEALTH_CHECK_PASSED_PACKAGE}.
172      * <p>
173      * <b>Note:</b> This API is primarily intended for testing purposes. Calling this outside of a
174      * test environment will override the default callback mechanism used to notify the system
175      * about health check results. Use with caution in production code.
176      *
177      * @param executor The executor on which the callback should be invoked. If {@code null}, the
178      *                 callback will be executed on the main thread.
179      * @param callback A callback that receives a {@link Bundle} containing the package name that
180      *                 passed the health check.
181      */
182     @FlaggedApi(Flags.FLAG_ENABLE_CRASHRECOVERY)
setHealthCheckPassedCallback(@allbackExecutor @ullable Executor executor, @Nullable Consumer<Bundle> callback)183     public final void setHealthCheckPassedCallback(@CallbackExecutor @Nullable Executor executor,
184             @Nullable Consumer<Bundle> callback) {
185         mCallbackExecutor = executor;
186         mHealthCheckResultCallback = callback;
187     }
188 
executeCallback(@onNull String packageName)189     private void executeCallback(@NonNull String packageName) {
190         if (mHealthCheckResultCallback != null) {
191             Objects.requireNonNull(packageName,
192                     "Package passing explicit health check must be non-null");
193             Bundle bundle = new Bundle();
194             bundle.putString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE, packageName);
195             mHealthCheckResultCallback.accept(bundle);
196         } else {
197             Log.wtf(TAG, "System missed explicit health check result for " + packageName);
198         }
199     }
200 
201     /**
202      * Implementors should call this to notify the system when explicit health check passes
203      * for {@code packageName};
204      */
notifyHealthCheckPassed(@onNull String packageName)205     public final void notifyHealthCheckPassed(@NonNull String packageName) {
206         if (mCallbackExecutor != null) {
207             mCallbackExecutor.execute(() -> executeCallback(packageName));
208         } else {
209             mHandler.post(() -> executeCallback(packageName));
210         }
211     }
212 
213     /**
214      * A PackageConfig contains a package supporting explicit health checks and the
215      * timeout in {@link System#uptimeMillis} across reboots after which health
216      * check requests from clients are failed.
217      *
218      * @hide
219      */
220     @SystemApi
221     public static final class PackageConfig implements Parcelable {
222         private static final long DEFAULT_HEALTH_CHECK_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(1);
223 
224         private final String mPackageName;
225         private final long mHealthCheckTimeoutMillis;
226 
227         /**
228          * Creates a new instance.
229          *
230          * @param packageName the package name
231          * @param durationMillis the duration in milliseconds, must be greater than or
232          * equal to 0. If it is 0, it will use a system default value.
233          */
PackageConfig(@onNull String packageName, long healthCheckTimeoutMillis)234         public PackageConfig(@NonNull String packageName, long healthCheckTimeoutMillis) {
235             mPackageName = Preconditions.checkNotNull(packageName);
236             if (healthCheckTimeoutMillis == 0) {
237                 mHealthCheckTimeoutMillis = DEFAULT_HEALTH_CHECK_TIMEOUT_MILLIS;
238             } else {
239                 mHealthCheckTimeoutMillis = Preconditions.checkArgumentNonnegative(
240                         healthCheckTimeoutMillis);
241             }
242         }
243 
PackageConfig(Parcel parcel)244         private PackageConfig(Parcel parcel) {
245             mPackageName = parcel.readString();
246             mHealthCheckTimeoutMillis = parcel.readLong();
247         }
248 
249         /**
250          * Gets the package name.
251          *
252          * @return the package name
253          */
getPackageName()254         public @NonNull String getPackageName() {
255             return mPackageName;
256         }
257 
258         /**
259          * Gets the timeout in milliseconds to evaluate an explicit health check result after a
260          * request.
261          *
262          * @return the duration in {@link System#uptimeMillis} across reboots
263          */
getHealthCheckTimeoutMillis()264         public long getHealthCheckTimeoutMillis() {
265             return mHealthCheckTimeoutMillis;
266         }
267 
268         @NonNull
269         @Override
toString()270         public String toString() {
271             return "PackageConfig{" + mPackageName + ", " + mHealthCheckTimeoutMillis + "}";
272         }
273 
274         @Override
equals(@ullable Object other)275         public boolean equals(@Nullable Object other) {
276             if (other == this) {
277                 return true;
278             }
279             if (!(other instanceof PackageConfig)) {
280                 return false;
281             }
282 
283             PackageConfig otherInfo = (PackageConfig) other;
284             return Objects.equals(otherInfo.getHealthCheckTimeoutMillis(),
285                     mHealthCheckTimeoutMillis)
286                     && Objects.equals(otherInfo.getPackageName(), mPackageName);
287         }
288 
289         @Override
hashCode()290         public int hashCode() {
291             return Objects.hash(mPackageName, mHealthCheckTimeoutMillis);
292         }
293 
294         @Override
describeContents()295         public int describeContents() {
296             return 0;
297         }
298 
299         @Override
writeToParcel(@uppressLint{"MissingNullability"}) Parcel parcel, int flags)300         public void writeToParcel(@SuppressLint({"MissingNullability"}) Parcel parcel, int flags) {
301             parcel.writeString(mPackageName);
302             parcel.writeLong(mHealthCheckTimeoutMillis);
303         }
304 
305         public static final @NonNull Creator<PackageConfig> CREATOR = new Creator<PackageConfig>() {
306                 @Override
307                 public PackageConfig createFromParcel(Parcel source) {
308                     return new PackageConfig(source);
309                 }
310 
311                 @Override
312                 public PackageConfig[] newArray(int size) {
313                     return new PackageConfig[size];
314                 }
315             };
316     }
317 
318 
319     private class ExplicitHealthCheckServiceWrapper extends IExplicitHealthCheckService.Stub {
320         @Override
setCallback(RemoteCallback callback)321         public void setCallback(RemoteCallback callback) throws RemoteException {
322             mHandler.post(() -> mHealthCheckResultCallback = callback::sendResult);
323         }
324 
325         @Override
request(String packageName)326         public void request(String packageName) throws RemoteException {
327             mHandler.post(() -> ExplicitHealthCheckService.this.onRequestHealthCheck(packageName));
328         }
329 
330         @Override
cancel(String packageName)331         public void cancel(String packageName) throws RemoteException {
332             mHandler.post(() -> ExplicitHealthCheckService.this.onCancelHealthCheck(packageName));
333         }
334 
335         @Override
getSupportedPackages(RemoteCallback callback)336         public void getSupportedPackages(RemoteCallback callback) throws RemoteException {
337             mHandler.post(() -> {
338                 List<PackageConfig> packages =
339                         ExplicitHealthCheckService.this.onGetSupportedPackages();
340                 Objects.requireNonNull(packages, "Supported package list must be non-null");
341                 Bundle bundle = new Bundle();
342                 bundle.putParcelableArrayList(EXTRA_SUPPORTED_PACKAGES, new ArrayList<>(packages));
343                 callback.sendResult(bundle);
344             });
345         }
346 
347         @Override
getRequestedPackages(RemoteCallback callback)348         public void getRequestedPackages(RemoteCallback callback) throws RemoteException {
349             mHandler.post(() -> {
350                 List<String> packages =
351                         ExplicitHealthCheckService.this.onGetRequestedPackages();
352                 Objects.requireNonNull(packages, "Requested  package list must be non-null");
353                 Bundle bundle = new Bundle();
354                 bundle.putStringArrayList(EXTRA_REQUESTED_PACKAGES, new ArrayList<>(packages));
355                 callback.sendResult(bundle);
356             });
357         }
358     }
359 }
360