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