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 * <service android:name=".FooExplicitHealthCheckService" 55 * android:exported="true" 56 * android:priority="100" 57 * android:permission="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"> 58 * <intent-filter> 59 * <action android:name="android.service.watchdog.ExplicitHealthCheckService" /> 60 * </intent-filter> 61 * </service> 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 @Override toString()233 public String toString() { 234 return "PackageConfig{" + mPackageName + ", " + mHealthCheckTimeoutMillis + "}"; 235 } 236 237 @Override equals(Object other)238 public boolean equals(Object other) { 239 if (other == this) { 240 return true; 241 } 242 if (!(other instanceof PackageConfig)) { 243 return false; 244 } 245 246 PackageConfig otherInfo = (PackageConfig) other; 247 return Objects.equals(otherInfo.getHealthCheckTimeoutMillis(), 248 mHealthCheckTimeoutMillis) 249 && Objects.equals(otherInfo.getPackageName(), mPackageName); 250 } 251 252 @Override hashCode()253 public int hashCode() { 254 return Objects.hash(mPackageName, mHealthCheckTimeoutMillis); 255 } 256 257 @Override describeContents()258 public int describeContents() { 259 return 0; 260 } 261 262 @Override writeToParcel(Parcel parcel, int flags)263 public void writeToParcel(Parcel parcel, int flags) { 264 parcel.writeString(mPackageName); 265 parcel.writeLong(mHealthCheckTimeoutMillis); 266 } 267 268 public static final @NonNull Creator<PackageConfig> CREATOR = new Creator<PackageConfig>() { 269 @Override 270 public PackageConfig createFromParcel(Parcel source) { 271 return new PackageConfig(source); 272 } 273 274 @Override 275 public PackageConfig[] newArray(int size) { 276 return new PackageConfig[size]; 277 } 278 }; 279 } 280 281 282 private class ExplicitHealthCheckServiceWrapper extends IExplicitHealthCheckService.Stub { 283 @Override setCallback(RemoteCallback callback)284 public void setCallback(RemoteCallback callback) throws RemoteException { 285 mHandler.post(() -> { 286 mCallback = callback; 287 }); 288 } 289 290 @Override request(String packageName)291 public void request(String packageName) throws RemoteException { 292 mHandler.post(() -> ExplicitHealthCheckService.this.onRequestHealthCheck(packageName)); 293 } 294 295 @Override cancel(String packageName)296 public void cancel(String packageName) throws RemoteException { 297 mHandler.post(() -> ExplicitHealthCheckService.this.onCancelHealthCheck(packageName)); 298 } 299 300 @Override getSupportedPackages(RemoteCallback callback)301 public void getSupportedPackages(RemoteCallback callback) throws RemoteException { 302 mHandler.post(() -> { 303 List<PackageConfig> packages = 304 ExplicitHealthCheckService.this.onGetSupportedPackages(); 305 Objects.requireNonNull(packages, "Supported package list must be non-null"); 306 Bundle bundle = new Bundle(); 307 bundle.putParcelableArrayList(EXTRA_SUPPORTED_PACKAGES, new ArrayList<>(packages)); 308 callback.sendResult(bundle); 309 }); 310 } 311 312 @Override getRequestedPackages(RemoteCallback callback)313 public void getRequestedPackages(RemoteCallback callback) throws RemoteException { 314 mHandler.post(() -> { 315 List<String> packages = 316 ExplicitHealthCheckService.this.onGetRequestedPackages(); 317 Objects.requireNonNull(packages, "Requested package list must be non-null"); 318 Bundle bundle = new Bundle(); 319 bundle.putStringArrayList(EXTRA_REQUESTED_PACKAGES, new ArrayList<>(packages)); 320 callback.sendResult(bundle); 321 }); 322 } 323 } 324 } 325