1 /* 2 * Copyright (C) 2020 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.car.watchdog; 18 19 import android.annotation.CallbackExecutor; 20 import android.annotation.IntDef; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.RequiresPermission; 24 import android.annotation.SystemApi; 25 import android.car.Car; 26 import android.car.CarManagerBase; 27 import android.os.Build; 28 import android.os.Handler; 29 import android.os.IBinder; 30 import android.os.Looper; 31 import android.os.RemoteException; 32 import android.os.UserHandle; 33 import android.util.Slog; 34 import android.util.SparseIntArray; 35 36 import com.android.internal.annotations.GuardedBy; 37 import com.android.internal.util.Preconditions; 38 39 import java.lang.annotation.ElementType; 40 import java.lang.annotation.Retention; 41 import java.lang.annotation.RetentionPolicy; 42 import java.lang.annotation.Target; 43 import java.lang.ref.WeakReference; 44 import java.util.ArrayList; 45 import java.util.List; 46 import java.util.Objects; 47 import java.util.concurrent.Executor; 48 49 /** 50 * CarWatchdogManager enables applications to track and manage system resource usage. 51 * 52 * <p>It allows applications to collect latest system resource overuse statistics, add listener for 53 * resource overuse notifications, and update resource overuse configurations. 54 */ 55 public final class CarWatchdogManager extends CarManagerBase { 56 57 private static final String TAG = CarWatchdogManager.class.getSimpleName(); 58 private static final boolean DEBUG = false; // STOPSHIP if true 59 private static final int INVALID_SESSION_ID = -1; 60 private static final int NUMBER_OF_CONDITIONS_TO_BE_MET = 2; 61 private static final int MAX_UNREGISTER_CLIENT_WAIT_MILLIS = 200; 62 63 private final Runnable mMainThreadCheck = () -> checkMainThread(); 64 65 /** 66 * Timeout for critical services that need to be responsive in 3000 milliseconds. 67 * 68 * @hide 69 */ 70 @SystemApi 71 public static final int TIMEOUT_CRITICAL = 0; 72 73 /** 74 * Timeout for moderate services that need to be responsive in 5000 milliseconds. 75 * 76 * @hide 77 */ 78 @SystemApi 79 public static final int TIMEOUT_MODERATE = 1; 80 81 /** 82 * Timeout for normal services that need to be responsive in 10000 milliseconds. 83 * 84 * @hide 85 */ 86 @SystemApi 87 public static final int TIMEOUT_NORMAL = 2; 88 89 /** @hide */ 90 @Retention(RetentionPolicy.SOURCE) 91 @IntDef(prefix = "TIMEOUT_", value = { 92 TIMEOUT_CRITICAL, 93 TIMEOUT_MODERATE, 94 TIMEOUT_NORMAL, 95 }) 96 @Target({ElementType.TYPE_USE}) 97 public @interface TimeoutLengthEnum {} 98 99 private final ICarWatchdogService mService; 100 private final ICarWatchdogClientImpl mClientImpl; 101 private final IResourceOveruseListenerImpl mResourceOveruseListenerImpl; 102 private final IResourceOveruseListenerImpl mResourceOveruseListenerForSystemImpl; 103 private final Handler mMainHandler = new Handler(Looper.getMainLooper()); 104 105 private final Object mLock = new Object(); 106 @GuardedBy("mLock") 107 private final SessionInfo mSession = new SessionInfo(INVALID_SESSION_ID, INVALID_SESSION_ID); 108 @GuardedBy("mLock") 109 private final List<ResourceOveruseListenerInfo> mResourceOveruseListenerInfos; 110 @GuardedBy("mLock") 111 private final List<ResourceOveruseListenerInfo> mResourceOveruseListenerForSystemInfos; 112 @GuardedBy("mLock") 113 private final ClientInfo mHealthCheckingClient = new ClientInfo(); 114 @GuardedBy("mLock") 115 private int mRemainingConditions; 116 117 /** 118 * A callback class for handling the CarWatchdog daemon's health check pings. 119 * 120 * <p>CarWatchdogClientCallback is implemented by the clients who want to be health-checked by 121 * the CarWatchdog daemon. On each onCheckHealthStatus call, clients are expected to respond by 122 * calling {@link CarWatchdogManager#tellClientAlive} within the timeout. If they fail to 123 * respond, the CarWatchdog daemon reports the current state and kills them. 124 * 125 * <p>Before termination, the CarWatchdog daemon calls {@link 126 * CarWatchdogClientCallback#onPrepareProcessTermination} to allow clients to prepare for the 127 * termination, which will occur after 1 second. 128 * 129 * @hide 130 */ 131 @SystemApi 132 public abstract static class CarWatchdogClientCallback { 133 /** 134 * Car watchdog server pings the client to check if it is alive. 135 * 136 * <p>The callback method is called at the Executor which is specified in {@link 137 * CarWatchdogManager#registerClient}. 138 * 139 * @param sessionId Unique id to distinguish each health checking. 140 * @param timeout Time duration within which the client should respond. 141 * 142 * @return whether the response is immediately acknowledged. If {@code true}, car watchdog 143 * server considers that the response is acknowledged already. If {@code false}, 144 * the client should call {@link CarWatchdogManager#tellClientAlive} later to tell 145 * that it is alive. 146 */ onCheckHealthStatus(int sessionId, @TimeoutLengthEnum int timeout)147 public boolean onCheckHealthStatus(int sessionId, @TimeoutLengthEnum int timeout) { 148 return false; 149 } 150 151 /** 152 * Car watchdog server notifies the client that it will be terminated in 1 second. 153 * 154 * <p>The callback method is called at the Executor which is specified in {@link 155 * CarWatchdogManager#registerClient}. 156 */ onPrepareProcessTermination()157 public void onPrepareProcessTermination() {} 158 } 159 160 /** @hide */ CarWatchdogManager(Car car, IBinder service)161 public CarWatchdogManager(Car car, IBinder service) { 162 super(car); 163 mService = ICarWatchdogService.Stub.asInterface(service); 164 mClientImpl = new ICarWatchdogClientImpl(this); 165 mResourceOveruseListenerImpl = new IResourceOveruseListenerImpl(this, /* isSystem= */false); 166 mResourceOveruseListenerForSystemImpl = new IResourceOveruseListenerImpl(this, 167 /* isSystem= */true); 168 mResourceOveruseListenerInfos = new ArrayList<>(); 169 mResourceOveruseListenerForSystemInfos = new ArrayList<>(); 170 } 171 172 /** 173 * Registers the car watchdog clients to {@link CarWatchdogManager}. 174 * 175 * <p>It is allowed to register a client from any thread, but only one client can be registered. 176 * If two or more clients are needed, create a new {@link Car} and register a client to it. 177 * 178 * @param client Watchdog client implementing {@link CarWatchdogClientCallback} interface. 179 * @param timeout The time duration within which the client desires to respond. The actual 180 * timeout is decided by watchdog server. 181 * @throws IllegalStateException if at least one client is already registered. 182 * 183 * @hide 184 */ 185 @SystemApi 186 @RequiresPermission(Car.PERMISSION_USE_CAR_WATCHDOG) registerClient(@onNull @allbackExecutor Executor executor, @NonNull CarWatchdogClientCallback client, @TimeoutLengthEnum int timeout)187 public void registerClient(@NonNull @CallbackExecutor Executor executor, 188 @NonNull CarWatchdogClientCallback client, @TimeoutLengthEnum int timeout) { 189 Objects.requireNonNull(client, "Client must be non-null"); 190 Objects.requireNonNull(executor, "Executor must be non-null"); 191 synchronized (CarWatchdogManager.this.mLock) { 192 if (mHealthCheckingClient.hasClientLocked()) { 193 if (mHealthCheckingClient.callback == client) { 194 return; 195 } 196 throw new IllegalStateException( 197 "Cannot register the client. Only one client can be registered."); 198 } 199 mHealthCheckingClient.setClientLocked(client, executor); 200 } 201 try { 202 mService.registerClient(mClientImpl, timeout); 203 if (DEBUG) { 204 Slog.d(TAG, "Car watchdog client is successfully registered"); 205 } 206 } catch (RemoteException e) { 207 synchronized (mLock) { 208 mHealthCheckingClient.resetClientLocked(); 209 } 210 handleRemoteExceptionFromCarService(e); 211 } finally { 212 synchronized (mLock) { 213 if (mHealthCheckingClient.hasClientLocked()) { 214 mHealthCheckingClient.setRegistrationCompletedLocked(); 215 } 216 } 217 } 218 } 219 220 /** 221 * Unregisters the car watchdog client from {@link CarWatchdogManager}. 222 * 223 * @param client Watchdog client implementing {@link CarWatchdogClientCallback} interface. 224 * 225 * @hide 226 */ 227 @SystemApi 228 @RequiresPermission(Car.PERMISSION_USE_CAR_WATCHDOG) unregisterClient(@onNull CarWatchdogClientCallback client)229 public void unregisterClient(@NonNull CarWatchdogClientCallback client) { 230 Objects.requireNonNull(client, "Client must be non-null"); 231 synchronized (CarWatchdogManager.this.mLock) { 232 if (!mHealthCheckingClient.hasClientLocked() 233 || mHealthCheckingClient.callback != client) { 234 Slog.w(TAG, "Cannot unregister the client. It has not been registered."); 235 return; 236 } 237 if (mHealthCheckingClient.isRegistrationInProgressLocked()) { 238 throwIllegalStateExceptionOnTargetSdkPostUdc( 239 "Cannot unregister the client while a registration is in progress"); 240 return; 241 } 242 mHealthCheckingClient.resetClientLocked(); 243 } 244 try { 245 mService.unregisterClient(mClientImpl); 246 if (DEBUG) { 247 Slog.d(TAG, "Car watchdog client is successfully unregistered"); 248 } 249 } catch (RemoteException e) { 250 handleRemoteExceptionFromCarService(e); 251 } 252 } 253 254 /** 255 * Tells {@link CarWatchdogManager} that the client is alive. 256 * 257 * @param client Watchdog client implementing {@link CarWatchdogClientCallback} interface. 258 * @param sessionId Session id given by {@link CarWatchdogManager}. 259 * @throws IllegalStateException if {@code client} is not registered. 260 * @throws IllegalArgumentException if {@code sessionId} is not correct. 261 * 262 * @hide 263 */ 264 @SystemApi 265 @RequiresPermission(Car.PERMISSION_USE_CAR_WATCHDOG) tellClientAlive(@onNull CarWatchdogClientCallback client, int sessionId)266 public void tellClientAlive(@NonNull CarWatchdogClientCallback client, int sessionId) { 267 Objects.requireNonNull(client, "Client must be non-null"); 268 boolean shouldReport; 269 synchronized (CarWatchdogManager.this.mLock) { 270 if (!mHealthCheckingClient.hasClientLocked() 271 || mHealthCheckingClient.callback != client) { 272 throw new IllegalStateException( 273 "Cannot report client status. The client has not been registered."); 274 } 275 Preconditions.checkArgument(sessionId != -1 && mSession.currentId == sessionId, 276 "Cannot report client status. Received session id (" + sessionId 277 + ") doesn't match the current one (" + mSession.currentId + ")."); 278 if (mSession.lastReportedId == sessionId) { 279 Slog.w(TAG, "The given session id is already reported."); 280 return; 281 } 282 mSession.lastReportedId = sessionId; 283 mRemainingConditions--; 284 shouldReport = checkConditionLocked(); 285 } 286 if (shouldReport) { 287 reportToService(sessionId); 288 } 289 } 290 291 /** @hide */ 292 @IntDef(flag = false, prefix = { "STATS_PERIOD_" }, value = { 293 STATS_PERIOD_CURRENT_DAY, 294 STATS_PERIOD_PAST_3_DAYS, 295 STATS_PERIOD_PAST_7_DAYS, 296 STATS_PERIOD_PAST_15_DAYS, 297 STATS_PERIOD_PAST_30_DAYS, 298 }) 299 @Retention(RetentionPolicy.SOURCE) 300 public @interface StatsPeriod {} 301 302 /** @hide */ 303 @IntDef(flag = true, prefix = { "FLAG_RESOURCE_OVERUSE_" }, value = { 304 FLAG_RESOURCE_OVERUSE_IO, 305 }) 306 @Retention(RetentionPolicy.SOURCE) 307 public @interface ResourceOveruseFlag {} 308 309 /** @hide */ 310 @IntDef(flag = true, prefix = { "FLAG_MINIMUM_STATS_" }, value = { 311 FLAG_MINIMUM_STATS_IO_1_MB, 312 FLAG_MINIMUM_STATS_IO_100_MB, 313 FLAG_MINIMUM_STATS_IO_1_GB, 314 }) 315 @Retention(RetentionPolicy.SOURCE) 316 public @interface MinimumStatsFlag {} 317 318 /** @hide */ 319 @IntDef(flag = true, prefix = { "RETURN_CODE_" }, value = { 320 RETURN_CODE_SUCCESS, 321 RETURN_CODE_ERROR, 322 }) 323 @Retention(RetentionPolicy.SOURCE) 324 public @interface ReturnCode {} 325 326 /** 327 * Constants that define the stats period in days. 328 * 329 * <p>The following constants represent the stats period for the past N days, It is used to 330 * specify that the stats should be gathered for the last N days, including today and the N-1 331 * previous days. 332 * 333 * <p>The stats period for the current day. 334 */ 335 public static final int STATS_PERIOD_CURRENT_DAY = 1; 336 337 /** The stats period for the past 3 days. */ 338 public static final int STATS_PERIOD_PAST_3_DAYS = 2; 339 340 /** The stats period for the past 7 days */ 341 public static final int STATS_PERIOD_PAST_7_DAYS = 3; 342 343 /** The stats period for the past 15 days */ 344 public static final int STATS_PERIOD_PAST_15_DAYS = 4; 345 346 /** The stats period for the past 30 days */ 347 public static final int STATS_PERIOD_PAST_30_DAYS = 5; 348 349 /** Constants that define the type of resource overuse. */ 350 public static final int FLAG_RESOURCE_OVERUSE_IO = 1 << 0; 351 352 /** 353 * Constants that define the minimum stats for each resource type. 354 * 355 * <p>The following constants represent the minimum amount of data written to disk. 356 * 357 * <p>The minimum amount of data that should be written to disk is 1 MB. 358 * 359 * @hide 360 */ 361 @SystemApi 362 public static final int FLAG_MINIMUM_STATS_IO_1_MB = 1 << 0; 363 364 /** 365 * The minimum amount of data that should be written to disk is 100 MB. 366 * 367 * @hide 368 */ 369 @SystemApi 370 public static final int FLAG_MINIMUM_STATS_IO_100_MB = 1 << 1; 371 372 /** 373 * The minimum amount of data that should be written to disk is 1 GB. 374 * 375 * @hide 376 */ 377 @SystemApi 378 public static final int FLAG_MINIMUM_STATS_IO_1_GB = 1 << 2; 379 380 /** 381 * Returns codes used to indicate the result of a request. 382 * 383 * <p>The return code indicating a successful request. 384 * 385 * @hide 386 */ 387 @SystemApi 388 public static final int RETURN_CODE_SUCCESS = 0; 389 390 /** 391 * The return code indicating an error in the request. 392 * 393 * @hide 394 */ 395 @SystemApi 396 public static final int RETURN_CODE_ERROR = -1; 397 398 /** 399 * Returns resource overuse stats for the calling package. Returns {@code null}, if no stats. 400 * 401 * @param resourceOveruseFlag Flag to indicate the types of resource overuse stats to return. 402 * @param maxStatsPeriod Maximum period to aggregate the resource overuse stats. 403 * 404 * @return Resource overuse stats for the calling package. If the calling package has no stats 405 * for a specified resource overuse type, null value is returned for the corresponding 406 * resource overuse stats. If the calling package doesn't have sufficient stats for 407 * {@code maxStatsPeriod} for a specified resource overuse type, the stats are returned 408 * only for the period returned in the individual resource overuse stats. 409 */ 410 @NonNull getResourceOveruseStats( @esourceOveruseFlag int resourceOveruseFlag, @StatsPeriod int maxStatsPeriod)411 public ResourceOveruseStats getResourceOveruseStats( 412 @ResourceOveruseFlag int resourceOveruseFlag, 413 @StatsPeriod int maxStatsPeriod) { 414 try { 415 return mService.getResourceOveruseStats(resourceOveruseFlag, maxStatsPeriod); 416 } catch (RemoteException e) { 417 ResourceOveruseStats.Builder builder = 418 new ResourceOveruseStats.Builder("", UserHandle.CURRENT); 419 return handleRemoteExceptionFromCarService(e, builder.build()); 420 } 421 } 422 423 /** 424 * Returns resource overuse stats for all monitored packages. 425 * 426 * @param resourceOveruseFlag Flag to indicate the types of resource overuse stats to return. 427 * @param minimumStatsFlag Flag to specify the minimum stats for each resource overuse type. 428 * Only stats greater than the specified minimum stats for a resource overuse type will be 429 * returned. May provide only one minimum stats flag for each resource overuse type. When no 430 * minimum stats flag is specified, all stats are returned. 431 * @param maxStatsPeriod Maximum period to aggregate the resource overuse stats. 432 * @return Resource overuse stats for all monitored packages. If any package doesn't have stats 433 * for a specified resource type, null value is returned for the corresponding resource 434 * overuse stats. If any package doesn't have sufficient stats for {@code maxStatsPeriod} 435 * for a specified resource overuse type, the stats are returned only for the period 436 * returned in the individual resource stats. 437 * @hide 438 */ 439 @SystemApi 440 @RequiresPermission(Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS) 441 @NonNull getAllResourceOveruseStats( @esourceOveruseFlag int resourceOveruseFlag, @MinimumStatsFlag int minimumStatsFlag, @StatsPeriod int maxStatsPeriod)442 public List<ResourceOveruseStats> getAllResourceOveruseStats( 443 @ResourceOveruseFlag int resourceOveruseFlag, 444 @MinimumStatsFlag int minimumStatsFlag, 445 @StatsPeriod int maxStatsPeriod) { 446 try { 447 return mService.getAllResourceOveruseStats(resourceOveruseFlag, minimumStatsFlag, 448 maxStatsPeriod); 449 } catch (RemoteException e) { 450 return handleRemoteExceptionFromCarService(e, new ArrayList<>()); 451 } 452 } 453 454 /** 455 * Returns resource overuse stats for a specific user package. 456 * 457 * @param packageName Name of the package whose stats should be returned. 458 * @param userHandle Handle of the user whose stats should be returned. 459 * @param resourceOveruseFlag Flag to indicate the types of resource overuse stats to return. 460 * @param maxStatsPeriod Maximum period to aggregate the resource overuse stats. 461 * 462 * @return Resource overuse stats for the specified user package. If the user package has no 463 * stats for a specified resource overuse type, null value is returned for the 464 * corresponding resource overuse stats. If the user package doesn't have sufficient 465 * stats for {@code maxStatsPeriod} for a specified resource overuse type, the stats are 466 * returned only for the period returned in the individual resource overuse stats. 467 * 468 * @hide 469 */ 470 @SystemApi 471 @RequiresPermission(Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS) 472 @NonNull getResourceOveruseStatsForUserPackage( @onNull String packageName, @NonNull UserHandle userHandle, @ResourceOveruseFlag int resourceOveruseFlag, @StatsPeriod int maxStatsPeriod)473 public ResourceOveruseStats getResourceOveruseStatsForUserPackage( 474 @NonNull String packageName, @NonNull UserHandle userHandle, 475 @ResourceOveruseFlag int resourceOveruseFlag, 476 @StatsPeriod int maxStatsPeriod) { 477 try { 478 return mService.getResourceOveruseStatsForUserPackage(packageName, userHandle, 479 resourceOveruseFlag, maxStatsPeriod); 480 } catch (RemoteException e) { 481 ResourceOveruseStats.Builder builder = 482 new ResourceOveruseStats.Builder("", userHandle); 483 return handleRemoteExceptionFromCarService(e, builder.build()); 484 } 485 } 486 487 /** 488 * Listener to get resource overuse notifications. 489 * 490 * <p>Applications implement the listener method to take action and/or log on resource overuse. 491 */ 492 public interface ResourceOveruseListener { 493 /** 494 * Called when a package either overuses a resource or about to overuse a resource. 495 * 496 * <p>The listener is called at the executor which is specified in {@link 497 * CarWatchdogManager#addResourceOveruseListener} or 498 * {@link CarWatchdogManager#addResourceOveruseListenerForSystem}. 499 * 500 * <p>The listener is called only on overusing one of the resources specified at the 501 * {@code resourceOveruseFlag} in {@link CarWatchdogManager#addResourceOveruseListener} or 502 * {@link CarWatchdogManager#addResourceOveruseListenerForSystem}. 503 * 504 * @param resourceOveruseStats Resource overuse stats containing stats only for resources 505 * overuse types that are either overused or about to be 506 * overused by the package. Implementations must check for null 507 * value in each resource overuse stats before reading the 508 * stats. 509 */ onOveruse(@onNull ResourceOveruseStats resourceOveruseStats)510 void onOveruse(@NonNull ResourceOveruseStats resourceOveruseStats); 511 } 512 513 /** 514 * Adds the {@link ResourceOveruseListener} for the calling package. 515 * 516 * <p>Resource overuse notifications are sent only for the calling package's resource overuse. 517 * 518 * @param listener Listener implementing {@link ResourceOveruseListener} interface. 519 * @param resourceOveruseFlag Flag to indicate the types of resource overuses to listen. 520 * 521 * @throws IllegalStateException if {@code listener} is already added. 522 */ addResourceOveruseListener( @onNull @allbackExecutor Executor executor, @ResourceOveruseFlag int resourceOveruseFlag, @NonNull ResourceOveruseListener listener)523 public void addResourceOveruseListener( 524 @NonNull @CallbackExecutor Executor executor, 525 @ResourceOveruseFlag int resourceOveruseFlag, 526 @NonNull ResourceOveruseListener listener) { 527 Objects.requireNonNull(listener, "Listener must be non-null"); 528 Objects.requireNonNull(executor, "Executor must be non-null"); 529 Preconditions.checkArgument((resourceOveruseFlag > 0), 530 "Must provide valid resource overuse flag"); 531 boolean shouldRemoveFromService; 532 boolean shouldAddToService; 533 synchronized (mLock) { 534 ResourceOveruseListenerInfo listenerInfo = 535 new ResourceOveruseListenerInfo(listener, executor, resourceOveruseFlag); 536 if (mResourceOveruseListenerInfos.contains(listenerInfo)) { 537 throw new IllegalStateException( 538 "Cannot add the listener as it is already added"); 539 } 540 shouldRemoveFromService = mResourceOveruseListenerImpl.hasListeners(); 541 shouldAddToService = mResourceOveruseListenerImpl.maybeAppendFlag(resourceOveruseFlag); 542 mResourceOveruseListenerInfos.add(listenerInfo); 543 } 544 if (shouldAddToService) { 545 if (shouldRemoveFromService) { 546 removeResourceOveruseListenerImpl(); 547 } 548 addResourceOveruseListenerImpl(); 549 } 550 } 551 552 /** 553 * Removes the {@link ResourceOveruseListener} for the calling package. 554 * 555 * @param listener Listener implementing {@link ResourceOveruseListener} interface. 556 */ removeResourceOveruseListener(@onNull ResourceOveruseListener listener)557 public void removeResourceOveruseListener(@NonNull ResourceOveruseListener listener) { 558 Objects.requireNonNull(listener, "Listener must be non-null"); 559 boolean shouldRemoveFromService; 560 boolean shouldReAddToService; 561 synchronized (mLock) { 562 int index = 0; 563 int resourceOveruseFlag = 0; 564 for (; index != mResourceOveruseListenerInfos.size(); ++index) { 565 ResourceOveruseListenerInfo listenerInfo = mResourceOveruseListenerInfos.get(index); 566 if (listenerInfo.listener == listener) { 567 resourceOveruseFlag = listenerInfo.resourceOveruseFlag; 568 break; 569 } 570 } 571 if (index == mResourceOveruseListenerInfos.size()) { 572 Slog.w(TAG, "Cannot remove the listener. It has not been added."); 573 return; 574 } 575 mResourceOveruseListenerInfos.remove(index); 576 shouldRemoveFromService = 577 mResourceOveruseListenerImpl.maybeRemoveFlag(resourceOveruseFlag); 578 shouldReAddToService = mResourceOveruseListenerImpl.hasListeners(); 579 } 580 if (shouldRemoveFromService) { 581 removeResourceOveruseListenerImpl(); 582 if (shouldReAddToService) { 583 addResourceOveruseListenerImpl(); 584 } 585 } 586 } 587 588 /** 589 * Adds {@link ResourceOveruseListener} to get resource overuse notifications for all packages. 590 * 591 * <p>Listening system services will get notified on any package overusing one of the resources 592 * specified at {@code resourceOveruseFlag}. 593 * 594 * @param listener Listener implementing {@link ResourceOveruseListener} interface. 595 * @param resourceOveruseFlag Flag to indicate the types of resource overuses to listen. 596 * 597 * @throws IllegalStateException if (@code listener} is already added. 598 * 599 * @hide 600 */ 601 @SystemApi 602 @RequiresPermission(Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS) addResourceOveruseListenerForSystem( @onNull @allbackExecutor Executor executor, @ResourceOveruseFlag int resourceOveruseFlag, @NonNull ResourceOveruseListener listener)603 public void addResourceOveruseListenerForSystem( 604 @NonNull @CallbackExecutor Executor executor, 605 @ResourceOveruseFlag int resourceOveruseFlag, 606 @NonNull ResourceOveruseListener listener) { 607 Objects.requireNonNull(listener, "Listener must be non-null"); 608 Objects.requireNonNull(executor, "Executor must be non-null"); 609 Preconditions.checkArgument((resourceOveruseFlag > 0), 610 "Must provide valid resource overuse flag"); 611 boolean shouldRemoveFromService; 612 boolean shouldAddToService; 613 synchronized (mLock) { 614 ResourceOveruseListenerInfo listenerInfo = 615 new ResourceOveruseListenerInfo(listener, executor, resourceOveruseFlag); 616 if (mResourceOveruseListenerForSystemInfos.contains(listenerInfo)) { 617 throw new IllegalStateException( 618 "Cannot add the listener as it is already added"); 619 } 620 shouldRemoveFromService = mResourceOveruseListenerForSystemImpl.hasListeners(); 621 shouldAddToService = 622 mResourceOveruseListenerForSystemImpl.maybeAppendFlag(resourceOveruseFlag); 623 mResourceOveruseListenerForSystemInfos.add(listenerInfo); 624 } 625 if (shouldAddToService) { 626 if (shouldRemoveFromService) { 627 removeResourceOveruseListenerForSystemImpl(); 628 } 629 addResourceOveruseListenerForSystemImpl(); 630 } 631 } 632 633 /** 634 * Removes {@link ResourceOveruseListener} from receiving system resource overuse notifications. 635 * 636 * @param listener Listener implementing {@link ResourceOveruseListener} interface. 637 * 638 * @hide 639 */ 640 @SystemApi 641 @RequiresPermission(Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS) removeResourceOveruseListenerForSystem( @onNull ResourceOveruseListener listener)642 public void removeResourceOveruseListenerForSystem( 643 @NonNull ResourceOveruseListener listener) { 644 Objects.requireNonNull(listener, "Listener must be non-null"); 645 boolean shouldRemoveFromService; 646 boolean shouldReAddToService; 647 synchronized (mLock) { 648 int index = 0; 649 int resourceOveruseFlag = 0; 650 for (; index != mResourceOveruseListenerForSystemInfos.size(); ++index) { 651 ResourceOveruseListenerInfo listenerInfo = 652 mResourceOveruseListenerForSystemInfos.get(index); 653 if (listenerInfo.listener == listener) { 654 resourceOveruseFlag = listenerInfo.resourceOveruseFlag; 655 break; 656 } 657 } 658 if (index == mResourceOveruseListenerForSystemInfos.size()) { 659 Slog.w(TAG, "Cannot remove the listener. It has not been added."); 660 return; 661 } 662 mResourceOveruseListenerForSystemInfos.remove(index); 663 shouldRemoveFromService = 664 mResourceOveruseListenerForSystemImpl.maybeRemoveFlag(resourceOveruseFlag); 665 shouldReAddToService = mResourceOveruseListenerForSystemImpl.hasListeners(); 666 } 667 if (shouldRemoveFromService) { 668 removeResourceOveruseListenerForSystemImpl(); 669 if (shouldReAddToService) { 670 addResourceOveruseListenerForSystemImpl(); 671 } 672 } 673 } 674 675 /** 676 * Sets whether or not a package is killable on resource overuse. 677 * 678 * <p>Updating killable setting for package, whose state cannot be changed, will result in 679 * exception. This API may be used by CarSettings application or UI notification. 680 * 681 * @param packageName Name of the package whose setting should to be updated. 682 * Note: All packages under shared UID share the killable state as well. Thus 683 * setting the killable state for one package will set the killable state for 684 * all other packages that share a UID. 685 * @param userHandle User whose setting should be updated. 686 * @param isKillable Whether or not the package for the specified user is killable on resource 687 * overuse. 688 * 689 * @hide 690 */ 691 @SystemApi 692 @RequiresPermission(Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG) setKillablePackageAsUser(@onNull String packageName, @NonNull UserHandle userHandle, boolean isKillable)693 public void setKillablePackageAsUser(@NonNull String packageName, 694 @NonNull UserHandle userHandle, boolean isKillable) { 695 try { 696 mService.setKillablePackageAsUser(packageName, userHandle, isKillable); 697 } catch (RemoteException e) { 698 handleRemoteExceptionFromCarService(e); 699 } 700 } 701 702 /** 703 * Returns the list of package killable states on resource overuse for the user. 704 * 705 * <p>This API may be used by CarSettings application or UI notification. 706 * 707 * @param userHandle User whose killable states for all packages should be returned. 708 * 709 * @hide 710 */ 711 @SystemApi 712 @RequiresPermission(Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG) 713 @NonNull getPackageKillableStatesAsUser( @onNull UserHandle userHandle)714 public List<PackageKillableState> getPackageKillableStatesAsUser( 715 @NonNull UserHandle userHandle) { 716 try { 717 return mService.getPackageKillableStatesAsUser(userHandle); 718 } catch (RemoteException e) { 719 return handleRemoteExceptionFromCarService(e, new ArrayList<>()); 720 } 721 } 722 723 /** 724 * Sets the resource overuse configurations for the components provided in the configurations. 725 * 726 * <p>Must provide only one configuration per component. System services should set the 727 * configurations only for system and third-party components. Vendor services should set the 728 * configuration only for the vendor component. 729 * 730 * @param configurations List of resource overuse configurations. One configuration per 731 * component. 732 * @param resourceOveruseFlag Flag to indicate the types of resource overuse configurations to 733 * set. 734 * 735 * @return - {@link #RETURN_CODE_SUCCESS} if the set request is successful. 736 * - {@link #RETURN_CODE_ERROR} if the set request cannot be completed and the client 737 * should retry later. 738 * 739 * @throws IllegalArgumentException if {@code configurations} are invalid. 740 * 741 * @hide 742 */ 743 @SystemApi 744 @RequiresPermission(Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG) 745 @ReturnCode setResourceOveruseConfigurations( @onNull List<ResourceOveruseConfiguration> configurations, @ResourceOveruseFlag int resourceOveruseFlag)746 public int setResourceOveruseConfigurations( 747 @NonNull List<ResourceOveruseConfiguration> configurations, 748 @ResourceOveruseFlag int resourceOveruseFlag) { 749 try { 750 return mService.setResourceOveruseConfigurations(configurations, resourceOveruseFlag); 751 } catch (RemoteException e) { 752 handleRemoteExceptionFromCarService(e); 753 return RETURN_CODE_ERROR; 754 } 755 } 756 757 /** 758 * Returns the current resource overuse configurations for all components. 759 * 760 * <p>This call is blocking and may take few seconds to return if the service is temporarily 761 * unavailable. 762 * 763 * @param resourceOveruseFlag Flag to indicate the types of resource overuse configurations to 764 * return. 765 * 766 * @return If the server process is alive and connected, returns list of available resource 767 * overuse configurations for all components. If the server process is dead, 768 * returns {@code null} value. 769 * 770 * @throws IllegalStateException if the system is in an invalid state. 771 * @hide 772 */ 773 @SystemApi 774 @RequiresPermission(anyOf = {Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG, 775 Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS}) 776 @Nullable getResourceOveruseConfigurations( @esourceOveruseFlag int resourceOveruseFlag)777 public List<ResourceOveruseConfiguration> getResourceOveruseConfigurations( 778 @ResourceOveruseFlag int resourceOveruseFlag) { 779 try { 780 return mService.getResourceOveruseConfigurations(resourceOveruseFlag); 781 } catch (RemoteException e) { 782 return handleRemoteExceptionFromCarService(e, null); 783 } 784 } 785 786 /** @hide */ 787 @Override onCarDisconnected()788 public void onCarDisconnected() { 789 // nothing to do 790 } 791 throwIllegalStateExceptionOnTargetSdkPostUdc(String message)792 private void throwIllegalStateExceptionOnTargetSdkPostUdc(String message) { 793 if (getContext().getApplicationInfo().targetSdkVersion 794 > Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { 795 throw new IllegalStateException(message); 796 } 797 Slog.e(TAG, "Suppressing illegal state exception on target SDK <= UDC: " + message); 798 } 799 checkClientStatus(int sessionId, int timeout)800 private void checkClientStatus(int sessionId, int timeout) { 801 CarWatchdogClientCallback clientCallback; 802 Executor executor; 803 mMainHandler.removeCallbacks(mMainThreadCheck); 804 synchronized (CarWatchdogManager.this.mLock) { 805 if (!mHealthCheckingClient.hasClientLocked()) { 806 Slog.w(TAG, "Cannot check client status. The client has not been registered."); 807 return; 808 } 809 mSession.currentId = sessionId; 810 clientCallback = mHealthCheckingClient.callback; 811 executor = mHealthCheckingClient.executor; 812 mRemainingConditions = NUMBER_OF_CONDITIONS_TO_BE_MET; 813 } 814 // For a car watchdog client to be active, 1) its main thread is active and 2) the client 815 // responds within timeout. When each condition is met, the remaining task counter is 816 // decreased. If the remaining task counter is zero, the client is considered active. 817 mMainHandler.post(mMainThreadCheck); 818 // Call the client callback to check if the client is active. 819 executor.execute(() -> { 820 boolean checkDone = clientCallback.onCheckHealthStatus(sessionId, timeout); 821 Slog.i(TAG, "Called clientCallback.onCheckHealthStatus"); 822 if (checkDone) { 823 boolean shouldReport; 824 synchronized (mLock) { 825 if (mSession.lastReportedId == sessionId) { 826 return; 827 } 828 mSession.lastReportedId = sessionId; 829 mRemainingConditions--; 830 shouldReport = checkConditionLocked(); 831 } 832 if (shouldReport) { 833 reportToService(sessionId); 834 } 835 } 836 }); 837 } 838 checkMainThread()839 private void checkMainThread() { 840 int sessionId; 841 boolean shouldReport; 842 synchronized (mLock) { 843 mRemainingConditions--; 844 sessionId = mSession.currentId; 845 shouldReport = checkConditionLocked(); 846 } 847 if (shouldReport) { 848 reportToService(sessionId); 849 } 850 } 851 852 @GuardedBy("mLock") checkConditionLocked()853 private boolean checkConditionLocked() { 854 if (mRemainingConditions < 0) { 855 Slog.wtf(TAG, "Remaining condition is less than zero: should not happen"); 856 } 857 return mRemainingConditions == 0; 858 } 859 reportToService(int sessionId)860 private void reportToService(int sessionId) { 861 try { 862 mService.tellClientAlive(mClientImpl, sessionId); 863 if (DEBUG) { 864 Slog.d(TAG, "Informed CarService that client is alive"); 865 } 866 } catch (RemoteException e) { 867 handleRemoteExceptionFromCarService(e); 868 } 869 } 870 notifyProcessTermination()871 private void notifyProcessTermination() { 872 CarWatchdogClientCallback clientCallback; 873 Executor executor; 874 synchronized (CarWatchdogManager.this.mLock) { 875 if (!mHealthCheckingClient.hasClientLocked()) { 876 Slog.w(TAG, "Cannot notify the client. The client has not been registered."); 877 return; 878 } 879 clientCallback = mHealthCheckingClient.callback; 880 executor = mHealthCheckingClient.executor; 881 } 882 executor.execute(() -> clientCallback.onPrepareProcessTermination()); 883 } 884 addResourceOveruseListenerImpl()885 private void addResourceOveruseListenerImpl() { 886 try { 887 mService.addResourceOveruseListener( 888 mResourceOveruseListenerImpl.resourceOveruseFlag(), 889 mResourceOveruseListenerImpl); 890 if (DEBUG) { 891 Slog.d(TAG, "Resource overuse listener implementation is successfully added to " 892 + "service"); 893 } 894 } catch (RemoteException e) { 895 synchronized (mLock) { 896 mResourceOveruseListenerInfos.clear(); 897 } 898 handleRemoteExceptionFromCarService(e); 899 } 900 } 901 removeResourceOveruseListenerImpl()902 private void removeResourceOveruseListenerImpl() { 903 try { 904 mService.removeResourceOveruseListener(mResourceOveruseListenerImpl); 905 if (DEBUG) { 906 Slog.d(TAG, "Resource overuse listener implementation is successfully removed " 907 + "from service"); 908 } 909 } catch (RemoteException e) { 910 handleRemoteExceptionFromCarService(e); 911 } 912 } 913 addResourceOveruseListenerForSystemImpl()914 private void addResourceOveruseListenerForSystemImpl() { 915 try { 916 mService.addResourceOveruseListenerForSystem( 917 mResourceOveruseListenerForSystemImpl.resourceOveruseFlag(), 918 mResourceOveruseListenerForSystemImpl); 919 if (DEBUG) { 920 Slog.d(TAG, "Resource overuse listener for system implementation is successfully " 921 + "added to service"); 922 } 923 } catch (RemoteException e) { 924 synchronized (mLock) { 925 mResourceOveruseListenerForSystemInfos.clear(); 926 } 927 handleRemoteExceptionFromCarService(e); 928 } 929 } 930 removeResourceOveruseListenerForSystemImpl()931 private void removeResourceOveruseListenerForSystemImpl() { 932 try { 933 mService.removeResourceOveruseListenerForSystem(mResourceOveruseListenerForSystemImpl); 934 if (DEBUG) { 935 Slog.d(TAG, "Resource overuse listener for system implementation is successfully " 936 + "removed from service"); 937 } 938 } catch (RemoteException e) { 939 handleRemoteExceptionFromCarService(e); 940 } 941 } 942 onResourceOveruse(ResourceOveruseStats resourceOveruseStats, boolean isSystem)943 private void onResourceOveruse(ResourceOveruseStats resourceOveruseStats, boolean isSystem) { 944 if (resourceOveruseStats.getIoOveruseStats() == null) { 945 Slog.w(TAG, "Skipping resource overuse notification as the stats are missing"); 946 return; 947 } 948 List<ResourceOveruseListenerInfo> listenerInfos; 949 synchronized (mLock) { 950 if (isSystem) { 951 listenerInfos = mResourceOveruseListenerForSystemInfos; 952 } else { 953 listenerInfos = mResourceOveruseListenerInfos; 954 } 955 } 956 if (listenerInfos.isEmpty()) { 957 Slog.w(TAG, "Cannot notify resource overuse listener " + (isSystem ? "for system " : "") 958 + "as it is not registered."); 959 return; 960 } 961 for (ResourceOveruseListenerInfo listenerInfo : listenerInfos) { 962 if ((listenerInfo.resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) == 1) { 963 listenerInfo.executor.execute(() -> { 964 listenerInfo.listener.onOveruse(resourceOveruseStats); 965 }); 966 } 967 } 968 } 969 970 /** @hide */ 971 private final class ClientInfo { 972 public CarWatchdogClientCallback callback; 973 public Executor executor; 974 private boolean mIsRegistrationInProgress; 975 976 @GuardedBy("CarWatchdogManager.this.mLock") hasClientLocked()977 boolean hasClientLocked() { 978 return callback != null && executor != null; 979 } 980 981 @GuardedBy("CarWatchdogManager.this.mLock") setClientLocked(CarWatchdogClientCallback callback, Executor executor)982 void setClientLocked(CarWatchdogClientCallback callback, Executor executor) { 983 this.callback = callback; 984 this.executor = executor; 985 mIsRegistrationInProgress = true; 986 if (DEBUG) { 987 Slog.d(TAG, "Set CarWatchdog client callback to " + callback); 988 } 989 } 990 991 @GuardedBy("CarWatchdogManager.this.mLock") resetClientLocked()992 void resetClientLocked() { 993 callback = null; 994 executor = null; 995 mIsRegistrationInProgress = false; 996 if (DEBUG) { 997 Slog.d(TAG, "Reset CarWatchdog client callback"); 998 } 999 } 1000 1001 @GuardedBy("CarWatchdogManager.this.mLock") setRegistrationCompletedLocked()1002 void setRegistrationCompletedLocked() { 1003 mIsRegistrationInProgress = false; 1004 mLock.notify(); 1005 if (DEBUG) { 1006 Slog.d(TAG, "Marked registration completed"); 1007 } 1008 } 1009 1010 @GuardedBy("CarWatchdogManager.this.mLock") isRegistrationInProgressLocked()1011 boolean isRegistrationInProgressLocked() { 1012 long nowMillis = System.currentTimeMillis(); 1013 long endMillis = nowMillis + MAX_UNREGISTER_CLIENT_WAIT_MILLIS; 1014 while (mIsRegistrationInProgress && nowMillis < endMillis) { 1015 try { 1016 mLock.wait(endMillis - nowMillis); 1017 } catch (InterruptedException e) { 1018 Thread.currentThread().interrupt(); 1019 Slog.w(TAG, "Interrupted while waiting for registration to complete. " 1020 + "Continuing to wait"); 1021 } finally { 1022 nowMillis = System.currentTimeMillis(); 1023 } 1024 } 1025 return mIsRegistrationInProgress; 1026 } 1027 } 1028 1029 /** @hide */ 1030 private static final class ICarWatchdogClientImpl extends ICarWatchdogServiceCallback.Stub { 1031 private final WeakReference<CarWatchdogManager> mManager; 1032 ICarWatchdogClientImpl(CarWatchdogManager manager)1033 ICarWatchdogClientImpl(CarWatchdogManager manager) { 1034 mManager = new WeakReference<>(manager); 1035 } 1036 1037 @Override onCheckHealthStatus(int sessionId, int timeout)1038 public void onCheckHealthStatus(int sessionId, int timeout) { 1039 CarWatchdogManager manager = mManager.get(); 1040 if (manager != null) { 1041 manager.checkClientStatus(sessionId, timeout); 1042 } 1043 } 1044 1045 @Override onPrepareProcessTermination()1046 public void onPrepareProcessTermination() { 1047 CarWatchdogManager manager = mManager.get(); 1048 if (manager != null) { 1049 manager.notifyProcessTermination(); 1050 } 1051 } 1052 } 1053 1054 private static final class SessionInfo { 1055 public int currentId; 1056 public int lastReportedId; 1057 SessionInfo(int currentId, int lastReportedId)1058 SessionInfo(int currentId, int lastReportedId) { 1059 this.currentId = currentId; 1060 this.lastReportedId = lastReportedId; 1061 } 1062 } 1063 1064 /** @hide */ 1065 private static final class IResourceOveruseListenerImpl extends IResourceOveruseListener.Stub { 1066 private static final int[] RESOURCE_OVERUSE_FLAGS = new int[]{ FLAG_RESOURCE_OVERUSE_IO }; 1067 1068 private final WeakReference<CarWatchdogManager> mManager; 1069 private final boolean mIsSystem; 1070 1071 private final Object mLock = new Object(); 1072 @GuardedBy("mLock") 1073 private final SparseIntArray mNumListenersByResource; 1074 1075 IResourceOveruseListenerImpl(CarWatchdogManager manager, boolean isSystem)1076 IResourceOveruseListenerImpl(CarWatchdogManager manager, boolean isSystem) { 1077 mManager = new WeakReference<>(manager); 1078 mIsSystem = isSystem; 1079 mNumListenersByResource = new SparseIntArray(); 1080 } 1081 1082 @Override onOveruse(ResourceOveruseStats resourceOveruserStats)1083 public void onOveruse(ResourceOveruseStats resourceOveruserStats) { 1084 CarWatchdogManager manager = mManager.get(); 1085 if (manager != null) { 1086 manager.onResourceOveruse(resourceOveruserStats, mIsSystem); 1087 } 1088 } 1089 hasListeners()1090 public boolean hasListeners() { 1091 synchronized (mLock) { 1092 return mNumListenersByResource.size() != 0; 1093 } 1094 } 1095 maybeAppendFlag(int appendFlag)1096 public boolean maybeAppendFlag(int appendFlag) { 1097 boolean isChanged = false; 1098 synchronized (mLock) { 1099 for (int flag : RESOURCE_OVERUSE_FLAGS) { 1100 if ((appendFlag & flag) != 1) { 1101 continue; 1102 } 1103 int value = mNumListenersByResource.get(flag, 0); 1104 isChanged = ++value == 1; 1105 mNumListenersByResource.put(flag, value); 1106 } 1107 } 1108 return isChanged; 1109 } 1110 maybeRemoveFlag(int removeFlag)1111 public boolean maybeRemoveFlag(int removeFlag) { 1112 boolean isChanged = false; 1113 synchronized (mLock) { 1114 for (int flag : RESOURCE_OVERUSE_FLAGS) { 1115 if ((removeFlag & flag) != 1) { 1116 continue; 1117 } 1118 int value = mNumListenersByResource.get(flag, 0); 1119 if (value == 0) { 1120 continue; 1121 } 1122 if (--value == 0) { 1123 isChanged = true; 1124 mNumListenersByResource.delete(flag); 1125 } else { 1126 mNumListenersByResource.put(flag, value); 1127 } 1128 } 1129 } 1130 return isChanged; 1131 } 1132 resourceOveruseFlag()1133 public int resourceOveruseFlag() { 1134 synchronized (mLock) { 1135 int flag = 0; 1136 for (int i = 0; i < mNumListenersByResource.size(); ++i) { 1137 flag |= mNumListenersByResource.valueAt(i) > 0 ? mNumListenersByResource.keyAt( 1138 i) : 0; 1139 } 1140 return flag; 1141 } 1142 } 1143 } 1144 1145 /** @hide */ 1146 private static final class ResourceOveruseListenerInfo { 1147 public final ResourceOveruseListener listener; 1148 public final Executor executor; 1149 public final int resourceOveruseFlag; 1150 ResourceOveruseListenerInfo(ResourceOveruseListener listener, Executor executor, int resourceOveruseFlag)1151 ResourceOveruseListenerInfo(ResourceOveruseListener listener, 1152 Executor executor, int resourceOveruseFlag) { 1153 this.listener = listener; 1154 this.executor = executor; 1155 this.resourceOveruseFlag = resourceOveruseFlag; 1156 } 1157 1158 @Override equals(Object obj)1159 public boolean equals(Object obj) { 1160 if (obj == this) { 1161 return true; 1162 } 1163 if (!(obj instanceof ResourceOveruseListenerInfo)) { 1164 return false; 1165 } 1166 ResourceOveruseListenerInfo listenerInfo = (ResourceOveruseListenerInfo) obj; 1167 // The ResourceOveruseListenerInfo equality is solely based on the listener because 1168 // the clients shouldn't register the same listener multiple times. When checking 1169 // whether a listener is previously registered, this equality check is used. 1170 return listenerInfo.listener == listener; 1171 } 1172 1173 @Override hashCode()1174 public int hashCode() { 1175 // Similar to equality check, the hash generator uses only the listener. 1176 return Objects.hash(listener); 1177 } 1178 } 1179 } 1180