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.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.SdkConstant;
24 import android.annotation.SystemApi;
25 import android.app.Service;
26 import android.content.Intent;
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.os.IBinder;
30 import android.os.Looper;
31 import android.os.Parcel;
32 import android.os.Parcelable;
33 import android.os.RemoteCallback;
34 import android.os.RemoteException;
35 import android.util.Log;
36 
37 import com.android.internal.util.Preconditions;
38 
39 import java.util.ArrayList;
40 import java.util.List;
41 import java.util.Objects;
42 import java.util.concurrent.TimeUnit;
43 
44 /**
45  * A service to provide packages supporting explicit health checks and route checks to these
46  * packages on behalf of the package watchdog.
47  *
48  * <p>To extend this class, you must declare the service in your manifest file with the
49  * {@link android.Manifest.permission#BIND_EXPLICIT_HEALTH_CHECK_SERVICE} permission,
50  * and include an intent filter with the {@link #SERVICE_INTERFACE} action. In adddition,
51  * your implementation must live in {@link PackageManger#SYSTEM_SHARED_LIBRARY_SERVICES}.
52  * For example:</p>
53  * <pre>
54  *     &lt;service android:name=".FooExplicitHealthCheckService"
55  *             android:exported="true"
56  *             android:priority="100"
57  *             android:permission="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"&gt;
58  *         &lt;intent-filter&gt;
59  *             &lt;action android:name="android.service.watchdog.ExplicitHealthCheckService" /&gt;
60  *         &lt;/intent-filter&gt;
61  *     &lt;/service&gt;
62  * </pre>
63  * @hide
64  */
65 @SystemApi
66 public abstract class ExplicitHealthCheckService extends Service {
67 
68     private static final String TAG = "ExplicitHealthCheckService";
69 
70     /**
71      * {@link Bundle} key for a {@link List} of {@link PackageConfig} value.
72      *
73      * {@hide}
74      */
75     public static final String EXTRA_SUPPORTED_PACKAGES =
76             "android.service.watchdog.extra.supported_packages";
77 
78     /**
79      * {@link Bundle} key for a {@link List} of {@link String} value.
80      *
81      * {@hide}
82      */
83     public static final String EXTRA_REQUESTED_PACKAGES =
84             "android.service.watchdog.extra.requested_packages";
85 
86     /**
87      * {@link Bundle} key for a {@link String} value.
88      *
89      * {@hide}
90      */
91     public static final String EXTRA_HEALTH_CHECK_PASSED_PACKAGE =
92             "android.service.watchdog.extra.health_check_passed_package";
93 
94     /**
95      * The Intent action that a service must respond to. Add it to the intent filter of the service
96      * in its manifest.
97      */
98     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
99     public static final String SERVICE_INTERFACE =
100             "android.service.watchdog.ExplicitHealthCheckService";
101 
102     /**
103      * The permission that a service must require to ensure that only Android system can bind to it.
104      * If this permission is not enforced in the AndroidManifest of the service, the system will
105      * skip that service.
106      */
107     public static final String BIND_PERMISSION =
108             "android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE";
109 
110     private final ExplicitHealthCheckServiceWrapper mWrapper =
111             new ExplicitHealthCheckServiceWrapper();
112 
113     /**
114      * Called when the system requests an explicit health check for {@code packageName}.
115      *
116      * <p> When {@code packageName} passes the check, implementors should call
117      * {@link #notifyHealthCheckPassed} to inform the system.
118      *
119      * <p> It could take many hours before a {@code packageName} passes a check and implementors
120      * should never drop requests unless {@link onCancel} is called or the service dies.
121      *
122      * <p> Requests should not be queued and additional calls while expecting a result for
123      * {@code packageName} should have no effect.
124      */
onRequestHealthCheck(@onNull String packageName)125     public abstract void onRequestHealthCheck(@NonNull String packageName);
126 
127     /**
128      * Called when the system cancels the explicit health check request for {@code packageName}.
129      * Should do nothing if there are is no active request for {@code packageName}.
130      */
onCancelHealthCheck(@onNull String packageName)131     public abstract void onCancelHealthCheck(@NonNull String packageName);
132 
133     /**
134      * Called when the system requests for all the packages supporting explicit health checks. The
135      * system may request an explicit health check for any of these packages with
136      * {@link #onRequestHealthCheck}.
137      *
138      * @return all packages supporting explicit health checks
139      */
onGetSupportedPackages()140     @NonNull public abstract List<PackageConfig> onGetSupportedPackages();
141 
142     /**
143      * Called when the system requests for all the packages that it has currently requested
144      * an explicit health check for.
145      *
146      * @return all packages expecting an explicit health check result
147      */
onGetRequestedPackages()148     @NonNull public abstract List<String> onGetRequestedPackages();
149 
150     private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
151     @Nullable private RemoteCallback mCallback;
152 
153     @Override
154     @NonNull
onBind(@onNull Intent intent)155     public final IBinder onBind(@NonNull Intent intent) {
156         return mWrapper;
157     }
158 
159     /**
160      * Implementors should call this to notify the system when explicit health check passes
161      * for {@code packageName};
162      */
notifyHealthCheckPassed(@onNull String packageName)163     public final void notifyHealthCheckPassed(@NonNull String packageName) {
164         mHandler.post(() -> {
165             if (mCallback != null) {
166                 Objects.requireNonNull(packageName,
167                         "Package passing explicit health check must be non-null");
168                 Bundle bundle = new Bundle();
169                 bundle.putString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE, packageName);
170                 mCallback.sendResult(bundle);
171             } else {
172                 Log.wtf(TAG, "System missed explicit health check result for " + packageName);
173             }
174         });
175     }
176 
177     /**
178      * A PackageConfig contains a package supporting explicit health checks and the
179      * timeout in {@link System#uptimeMillis} across reboots after which health
180      * check requests from clients are failed.
181      *
182      * @hide
183      */
184     @SystemApi
185     public static final class PackageConfig implements Parcelable {
186         private static final long DEFAULT_HEALTH_CHECK_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(1);
187 
188         private final String mPackageName;
189         private final long mHealthCheckTimeoutMillis;
190 
191         /**
192          * Creates a new instance.
193          *
194          * @param packageName the package name
195          * @param durationMillis the duration in milliseconds, must be greater than or
196          * equal to 0. If it is 0, it will use a system default value.
197          */
PackageConfig(@onNull String packageName, long healthCheckTimeoutMillis)198         public PackageConfig(@NonNull String packageName, long healthCheckTimeoutMillis) {
199             mPackageName = Preconditions.checkNotNull(packageName);
200             if (healthCheckTimeoutMillis == 0) {
201                 mHealthCheckTimeoutMillis = DEFAULT_HEALTH_CHECK_TIMEOUT_MILLIS;
202             } else {
203                 mHealthCheckTimeoutMillis = Preconditions.checkArgumentNonnegative(
204                         healthCheckTimeoutMillis);
205             }
206         }
207 
PackageConfig(Parcel parcel)208         private PackageConfig(Parcel parcel) {
209             mPackageName = parcel.readString();
210             mHealthCheckTimeoutMillis = parcel.readLong();
211         }
212 
213         /**
214          * Gets the package name.
215          *
216          * @return the package name
217          */
getPackageName()218         public @NonNull String getPackageName() {
219             return mPackageName;
220         }
221 
222         /**
223          * Gets the timeout in milliseconds to evaluate an explicit health check result after a
224          * request.
225          *
226          * @return the duration in {@link System#uptimeMillis} across reboots
227          */
getHealthCheckTimeoutMillis()228         public long getHealthCheckTimeoutMillis() {
229             return mHealthCheckTimeoutMillis;
230         }
231 
232         @NonNull
233         @Override
toString()234         public String toString() {
235             return "PackageConfig{" + mPackageName + ", " + mHealthCheckTimeoutMillis + "}";
236         }
237 
238         @Override
equals(@ullable Object other)239         public boolean equals(@Nullable Object other) {
240             if (other == this) {
241                 return true;
242             }
243             if (!(other instanceof PackageConfig)) {
244                 return false;
245             }
246 
247             PackageConfig otherInfo = (PackageConfig) other;
248             return Objects.equals(otherInfo.getHealthCheckTimeoutMillis(),
249                     mHealthCheckTimeoutMillis)
250                     && Objects.equals(otherInfo.getPackageName(), mPackageName);
251         }
252 
253         @Override
hashCode()254         public int hashCode() {
255             return Objects.hash(mPackageName, mHealthCheckTimeoutMillis);
256         }
257 
258         @Override
describeContents()259         public int describeContents() {
260             return 0;
261         }
262 
263         @Override
writeToParcel(Parcel parcel, int flags)264         public void writeToParcel(Parcel parcel, int flags) {
265             parcel.writeString(mPackageName);
266             parcel.writeLong(mHealthCheckTimeoutMillis);
267         }
268 
269         public static final @NonNull Creator<PackageConfig> CREATOR = new Creator<PackageConfig>() {
270                 @Override
271                 public PackageConfig createFromParcel(Parcel source) {
272                     return new PackageConfig(source);
273                 }
274 
275                 @Override
276                 public PackageConfig[] newArray(int size) {
277                     return new PackageConfig[size];
278                 }
279             };
280     }
281 
282 
283     private class ExplicitHealthCheckServiceWrapper extends IExplicitHealthCheckService.Stub {
284         @Override
setCallback(RemoteCallback callback)285         public void setCallback(RemoteCallback callback) throws RemoteException {
286             mHandler.post(() -> {
287                 mCallback = callback;
288             });
289         }
290 
291         @Override
request(String packageName)292         public void request(String packageName) throws RemoteException {
293             mHandler.post(() -> ExplicitHealthCheckService.this.onRequestHealthCheck(packageName));
294         }
295 
296         @Override
cancel(String packageName)297         public void cancel(String packageName) throws RemoteException {
298             mHandler.post(() -> ExplicitHealthCheckService.this.onCancelHealthCheck(packageName));
299         }
300 
301         @Override
getSupportedPackages(RemoteCallback callback)302         public void getSupportedPackages(RemoteCallback callback) throws RemoteException {
303             mHandler.post(() -> {
304                 List<PackageConfig> packages =
305                         ExplicitHealthCheckService.this.onGetSupportedPackages();
306                 Objects.requireNonNull(packages, "Supported package list must be non-null");
307                 Bundle bundle = new Bundle();
308                 bundle.putParcelableArrayList(EXTRA_SUPPORTED_PACKAGES, new ArrayList<>(packages));
309                 callback.sendResult(bundle);
310             });
311         }
312 
313         @Override
getRequestedPackages(RemoteCallback callback)314         public void getRequestedPackages(RemoteCallback callback) throws RemoteException {
315             mHandler.post(() -> {
316                 List<String> packages =
317                         ExplicitHealthCheckService.this.onGetRequestedPackages();
318                 Objects.requireNonNull(packages, "Requested  package list must be non-null");
319                 Bundle bundle = new Bundle();
320                 bundle.putStringArrayList(EXTRA_REQUESTED_PACKAGES, new ArrayList<>(packages));
321                 callback.sendResult(bundle);
322             });
323         }
324     }
325 }
326