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