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 * <service android:name=".FooExplicitHealthCheckService" 63 * android:exported="true" 64 * android:priority="100" 65 * android:permission="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"> 66 * <intent-filter> 67 * <action android:name="android.service.watchdog.ExplicitHealthCheckService" /> 68 * </intent-filter> 69 * </service> 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