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 com.android.server; 18 19 import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE; 20 import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_REQUESTED_PACKAGES; 21 import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_SUPPORTED_PACKAGES; 22 import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig; 23 24 import android.Manifest; 25 import android.annotation.MainThread; 26 import android.annotation.Nullable; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.ServiceConnection; 31 import android.content.pm.PackageManager; 32 import android.content.pm.ResolveInfo; 33 import android.content.pm.ServiceInfo; 34 import android.os.IBinder; 35 import android.os.RemoteCallback; 36 import android.os.RemoteException; 37 import android.os.UserHandle; 38 import android.service.watchdog.ExplicitHealthCheckService; 39 import android.service.watchdog.IExplicitHealthCheckService; 40 import android.text.TextUtils; 41 import android.util.ArraySet; 42 import android.util.Slog; 43 44 import com.android.internal.annotations.GuardedBy; 45 46 import java.util.Collection; 47 import java.util.Collections; 48 import java.util.Iterator; 49 import java.util.List; 50 import java.util.Objects; 51 import java.util.Set; 52 import java.util.function.Consumer; 53 54 // TODO(b/120598832): Add tests 55 /** 56 * Controls the connections with {@link ExplicitHealthCheckService}. 57 */ 58 class ExplicitHealthCheckController { 59 private static final String TAG = "ExplicitHealthCheckController"; 60 private final Object mLock = new Object(); 61 private final Context mContext; 62 63 // Called everytime a package passes the health check, so the watchdog is notified of the 64 // passing check. In practice, should never be null after it has been #setEnabled. 65 // To prevent deadlocks between the controller and watchdog threads, we have 66 // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class. 67 // It's easier to just NOT hold #mLock when calling into watchdog code on this consumer. 68 @GuardedBy("mLock") @Nullable private Consumer<String> mPassedConsumer; 69 // Called everytime after a successful #syncRequest call, so the watchdog can receive packages 70 // supporting health checks and update its internal state. In practice, should never be null 71 // after it has been #setEnabled. 72 // To prevent deadlocks between the controller and watchdog threads, we have 73 // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class. 74 // It's easier to just NOT hold #mLock when calling into watchdog code on this consumer. 75 @GuardedBy("mLock") @Nullable private Consumer<List<PackageConfig>> mSupportedConsumer; 76 // Called everytime we need to notify the watchdog to sync requests between itself and the 77 // health check service. In practice, should never be null after it has been #setEnabled. 78 // To prevent deadlocks between the controller and watchdog threads, we have 79 // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class. 80 // It's easier to just NOT hold #mLock when calling into watchdog code on this runnable. 81 @GuardedBy("mLock") @Nullable private Runnable mNotifySyncRunnable; 82 // Actual binder object to the explicit health check service. 83 @GuardedBy("mLock") @Nullable private IExplicitHealthCheckService mRemoteService; 84 // Connection to the explicit health check service, necessary to unbind. 85 // We should only try to bind if mConnection is null, non-null indicates we 86 // are connected or at least connecting. 87 @GuardedBy("mLock") @Nullable private ServiceConnection mConnection; 88 // Bind state of the explicit health check service. 89 @GuardedBy("mLock") private boolean mEnabled; 90 ExplicitHealthCheckController(Context context)91 ExplicitHealthCheckController(Context context) { 92 mContext = context; 93 } 94 95 /** Enables or disables explicit health checks. */ setEnabled(boolean enabled)96 public void setEnabled(boolean enabled) { 97 synchronized (mLock) { 98 Slog.i(TAG, "Explicit health checks " + (enabled ? "enabled." : "disabled.")); 99 mEnabled = enabled; 100 } 101 } 102 103 /** 104 * Sets callbacks to listen to important events from the controller. 105 * 106 * <p> Should be called once at initialization before any other calls to the controller to 107 * ensure a happens-before relationship of the set parameters and visibility on other threads. 108 */ setCallbacks(Consumer<String> passedConsumer, Consumer<List<PackageConfig>> supportedConsumer, Runnable notifySyncRunnable)109 public void setCallbacks(Consumer<String> passedConsumer, 110 Consumer<List<PackageConfig>> supportedConsumer, Runnable notifySyncRunnable) { 111 synchronized (mLock) { 112 if (mPassedConsumer != null || mSupportedConsumer != null 113 || mNotifySyncRunnable != null) { 114 Slog.wtf(TAG, "Resetting health check controller callbacks"); 115 } 116 117 mPassedConsumer = Objects.requireNonNull(passedConsumer); 118 mSupportedConsumer = Objects.requireNonNull(supportedConsumer); 119 mNotifySyncRunnable = Objects.requireNonNull(notifySyncRunnable); 120 } 121 } 122 123 /** 124 * Calls the health check service to request or cancel packages based on 125 * {@code newRequestedPackages}. 126 * 127 * <p> Supported packages in {@code newRequestedPackages} that have not been previously 128 * requested will be requested while supported packages not in {@code newRequestedPackages} 129 * but were previously requested will be cancelled. 130 * 131 * <p> This handles binding and unbinding to the health check service as required. 132 * 133 * <p> Note, calling this may modify {@code newRequestedPackages}. 134 * 135 * <p> Note, this method is not thread safe, all calls should be serialized. 136 */ syncRequests(Set<String> newRequestedPackages)137 public void syncRequests(Set<String> newRequestedPackages) { 138 boolean enabled; 139 synchronized (mLock) { 140 enabled = mEnabled; 141 } 142 143 if (!enabled) { 144 Slog.i(TAG, "Health checks disabled, no supported packages"); 145 // Call outside lock 146 mSupportedConsumer.accept(Collections.emptyList()); 147 return; 148 } 149 150 getSupportedPackages(supportedPackageConfigs -> { 151 // Notify the watchdog without lock held 152 mSupportedConsumer.accept(supportedPackageConfigs); 153 getRequestedPackages(previousRequestedPackages -> { 154 synchronized (mLock) { 155 // Hold lock so requests and cancellations are sent atomically. 156 // It is important we don't mix requests from multiple threads. 157 158 Set<String> supportedPackages = new ArraySet<>(); 159 for (PackageConfig config : supportedPackageConfigs) { 160 supportedPackages.add(config.getPackageName()); 161 } 162 // Note, this may modify newRequestedPackages 163 newRequestedPackages.retainAll(supportedPackages); 164 165 // Cancel packages no longer requested 166 actOnDifference(previousRequestedPackages, 167 newRequestedPackages, p -> cancel(p)); 168 // Request packages not yet requested 169 actOnDifference(newRequestedPackages, 170 previousRequestedPackages, p -> request(p)); 171 172 if (newRequestedPackages.isEmpty()) { 173 Slog.i(TAG, "No more health check requests, unbinding..."); 174 unbindService(); 175 return; 176 } 177 } 178 }); 179 }); 180 } 181 actOnDifference(Collection<String> collection1, Collection<String> collection2, Consumer<String> action)182 private void actOnDifference(Collection<String> collection1, Collection<String> collection2, 183 Consumer<String> action) { 184 Iterator<String> iterator = collection1.iterator(); 185 while (iterator.hasNext()) { 186 String packageName = iterator.next(); 187 if (!collection2.contains(packageName)) { 188 action.accept(packageName); 189 } 190 } 191 } 192 193 /** 194 * Requests an explicit health check for {@code packageName}. 195 * After this request, the callback registered on {@link #setCallbacks} can receive explicit 196 * health check passed results. 197 */ request(String packageName)198 private void request(String packageName) { 199 synchronized (mLock) { 200 if (!prepareServiceLocked("request health check for " + packageName)) { 201 return; 202 } 203 204 Slog.i(TAG, "Requesting health check for package " + packageName); 205 try { 206 mRemoteService.request(packageName); 207 } catch (RemoteException e) { 208 Slog.w(TAG, "Failed to request health check for package " + packageName, e); 209 } 210 } 211 } 212 213 /** 214 * Cancels all explicit health checks for {@code packageName}. 215 * After this request, the callback registered on {@link #setCallbacks} can no longer receive 216 * explicit health check passed results. 217 */ cancel(String packageName)218 private void cancel(String packageName) { 219 synchronized (mLock) { 220 if (!prepareServiceLocked("cancel health check for " + packageName)) { 221 return; 222 } 223 224 Slog.i(TAG, "Cancelling health check for package " + packageName); 225 try { 226 mRemoteService.cancel(packageName); 227 } catch (RemoteException e) { 228 // Do nothing, if the service is down, when it comes up, we will sync requests, 229 // if there's some other error, retrying wouldn't fix anyways. 230 Slog.w(TAG, "Failed to cancel health check for package " + packageName, e); 231 } 232 } 233 } 234 235 /** 236 * Returns the packages that we can request explicit health checks for. 237 * The packages will be returned to the {@code consumer}. 238 */ getSupportedPackages(Consumer<List<PackageConfig>> consumer)239 private void getSupportedPackages(Consumer<List<PackageConfig>> consumer) { 240 synchronized (mLock) { 241 if (!prepareServiceLocked("get health check supported packages")) { 242 return; 243 } 244 245 Slog.d(TAG, "Getting health check supported packages"); 246 try { 247 mRemoteService.getSupportedPackages(new RemoteCallback(result -> { 248 List<PackageConfig> packages = 249 result.getParcelableArrayList(EXTRA_SUPPORTED_PACKAGES, android.service.watchdog.ExplicitHealthCheckService.PackageConfig.class); 250 Slog.i(TAG, "Explicit health check supported packages " + packages); 251 consumer.accept(packages); 252 })); 253 } catch (RemoteException e) { 254 // Request failed, treat as if all observed packages are supported, if any packages 255 // expire during this period, we may incorrectly treat it as failing health checks 256 // even if we don't support health checks for the package. 257 Slog.w(TAG, "Failed to get health check supported packages", e); 258 } 259 } 260 } 261 262 /** 263 * Returns the packages for which health checks are currently in progress. 264 * The packages will be returned to the {@code consumer}. 265 */ getRequestedPackages(Consumer<List<String>> consumer)266 private void getRequestedPackages(Consumer<List<String>> consumer) { 267 synchronized (mLock) { 268 if (!prepareServiceLocked("get health check requested packages")) { 269 return; 270 } 271 272 Slog.d(TAG, "Getting health check requested packages"); 273 try { 274 mRemoteService.getRequestedPackages(new RemoteCallback(result -> { 275 List<String> packages = result.getStringArrayList(EXTRA_REQUESTED_PACKAGES); 276 Slog.i(TAG, "Explicit health check requested packages " + packages); 277 consumer.accept(packages); 278 })); 279 } catch (RemoteException e) { 280 // Request failed, treat as if we haven't requested any packages, if any packages 281 // were actually requested, they will not be cancelled now. May be cancelled later 282 Slog.w(TAG, "Failed to get health check requested packages", e); 283 } 284 } 285 } 286 287 /** 288 * Binds to the explicit health check service if the controller is enabled and 289 * not already bound. 290 */ bindService()291 private void bindService() { 292 synchronized (mLock) { 293 if (!mEnabled || mConnection != null || mRemoteService != null) { 294 if (!mEnabled) { 295 Slog.i(TAG, "Not binding to service, service disabled"); 296 } else if (mRemoteService != null) { 297 Slog.i(TAG, "Not binding to service, service already connected"); 298 } else { 299 Slog.i(TAG, "Not binding to service, service already connecting"); 300 } 301 return; 302 } 303 ComponentName component = getServiceComponentNameLocked(); 304 if (component == null) { 305 Slog.wtf(TAG, "Explicit health check service not found"); 306 return; 307 } 308 309 Intent intent = new Intent(); 310 intent.setComponent(component); 311 mConnection = new ServiceConnection() { 312 @Override 313 public void onServiceConnected(ComponentName name, IBinder service) { 314 Slog.i(TAG, "Explicit health check service is connected " + name); 315 initState(service); 316 } 317 318 @Override 319 @MainThread 320 public void onServiceDisconnected(ComponentName name) { 321 // Service crashed or process was killed, #onServiceConnected will be called. 322 // Don't need to re-bind. 323 Slog.i(TAG, "Explicit health check service is disconnected " + name); 324 synchronized (mLock) { 325 mRemoteService = null; 326 } 327 } 328 329 @Override 330 public void onBindingDied(ComponentName name) { 331 // Application hosting service probably got updated 332 // Need to re-bind. 333 Slog.i(TAG, "Explicit health check service binding is dead. Rebind: " + name); 334 unbindService(); 335 bindService(); 336 } 337 338 @Override 339 public void onNullBinding(ComponentName name) { 340 // Should never happen. Service returned null from #onBind. 341 Slog.wtf(TAG, "Explicit health check service binding is null?? " + name); 342 } 343 }; 344 345 mContext.bindServiceAsUser(intent, mConnection, 346 Context.BIND_AUTO_CREATE, UserHandle.SYSTEM); 347 Slog.i(TAG, "Explicit health check service is bound"); 348 } 349 } 350 351 /** Unbinds the explicit health check service. */ unbindService()352 private void unbindService() { 353 synchronized (mLock) { 354 if (mRemoteService != null) { 355 mContext.unbindService(mConnection); 356 mRemoteService = null; 357 mConnection = null; 358 } 359 Slog.i(TAG, "Explicit health check service is unbound"); 360 } 361 } 362 363 @GuardedBy("mLock") 364 @Nullable getServiceInfoLocked()365 private ServiceInfo getServiceInfoLocked() { 366 final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE); 367 final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent, 368 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA 369 | PackageManager.MATCH_SYSTEM_ONLY); 370 if (resolveInfo == null || resolveInfo.serviceInfo == null) { 371 Slog.w(TAG, "No valid components found."); 372 return null; 373 } 374 return resolveInfo.serviceInfo; 375 } 376 377 @GuardedBy("mLock") 378 @Nullable getServiceComponentNameLocked()379 private ComponentName getServiceComponentNameLocked() { 380 final ServiceInfo serviceInfo = getServiceInfoLocked(); 381 if (serviceInfo == null) { 382 return null; 383 } 384 385 final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name); 386 if (!Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE 387 .equals(serviceInfo.permission)) { 388 Slog.w(TAG, name.flattenToShortString() + " does not require permission " 389 + Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE); 390 return null; 391 } 392 return name; 393 } 394 initState(IBinder service)395 private void initState(IBinder service) { 396 synchronized (mLock) { 397 if (!mEnabled) { 398 Slog.w(TAG, "Attempting to connect disabled service?? Unbinding..."); 399 // Very unlikely, but we disabled the service after binding but before we connected 400 unbindService(); 401 return; 402 } 403 mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service); 404 try { 405 mRemoteService.setCallback(new RemoteCallback(result -> { 406 String packageName = result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE); 407 if (!TextUtils.isEmpty(packageName)) { 408 if (mPassedConsumer == null) { 409 Slog.wtf(TAG, "Health check passed for package " + packageName 410 + "but no consumer registered."); 411 } else { 412 // Call without lock held 413 mPassedConsumer.accept(packageName); 414 } 415 } else { 416 Slog.wtf(TAG, "Empty package passed explicit health check?"); 417 } 418 })); 419 Slog.i(TAG, "Service initialized, syncing requests"); 420 } catch (RemoteException e) { 421 Slog.wtf(TAG, "Could not setCallback on explicit health check service"); 422 } 423 } 424 // Calling outside lock 425 mNotifySyncRunnable.run(); 426 } 427 428 /** 429 * Prepares the health check service to receive requests. 430 * 431 * @return {@code true} if it is ready and we can proceed with a request, 432 * {@code false} otherwise. If it is not ready, and the service is enabled, 433 * we will bind and the request should be automatically attempted later. 434 */ 435 @GuardedBy("mLock") prepareServiceLocked(String action)436 private boolean prepareServiceLocked(String action) { 437 if (mRemoteService != null && mEnabled) { 438 return true; 439 } 440 Slog.i(TAG, "Service not ready to " + action 441 + (mEnabled ? ". Binding..." : ". Disabled")); 442 if (mEnabled) { 443 bindService(); 444 } 445 return false; 446 } 447 } 448