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