1 /* 2 * Copyright (C) 2021 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.car.watchdog; 18 19 import static android.car.watchdog.CarWatchdogManager.TIMEOUT_CRITICAL; 20 import static android.car.watchdog.CarWatchdogManager.TIMEOUT_MODERATE; 21 import static android.car.watchdog.CarWatchdogManager.TIMEOUT_NORMAL; 22 import static android.car.watchdog.CarWatchdogManager.TimeoutLengthEnum; 23 24 import static com.android.car.CarServiceUtils.getHandlerThread; 25 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 26 27 import android.annotation.NonNull; 28 import android.annotation.UserIdInt; 29 import android.automotive.watchdog.internal.ICarWatchdogServiceForSystem; 30 import android.automotive.watchdog.internal.ProcessIdentifier; 31 import android.car.builtin.util.Slogf; 32 import android.car.watchdog.ICarWatchdogServiceCallback; 33 import android.car.watchdoglib.CarWatchdogDaemonHelper; 34 import android.os.Binder; 35 import android.os.Handler; 36 import android.os.IBinder; 37 import android.os.Looper; 38 import android.os.RemoteException; 39 import android.os.SystemClock; 40 import android.os.SystemProperties; 41 import android.os.UserHandle; 42 import android.util.SparseArray; 43 import android.util.SparseBooleanArray; 44 import android.util.proto.ProtoOutputStream; 45 46 import com.android.car.CarServiceHelperWrapper; 47 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 48 import com.android.car.internal.dep.Trace; 49 import com.android.car.internal.util.IndentingPrintWriter; 50 import com.android.internal.annotations.GuardedBy; 51 52 import java.util.ArrayList; 53 import java.util.List; 54 import java.util.Optional; 55 56 /** 57 * Handles clients' health status checking and reporting the statuses to the watchdog daemon. 58 */ 59 public final class WatchdogProcessHandler { 60 static final String PROPERTY_RO_CLIENT_HEALTHCHECK_INTERVAL = 61 "ro.carwatchdog.client_healthcheck.interval"; 62 static final int MISSING_INT_PROPERTY_VALUE = -1; 63 64 private static final int[] ALL_TIMEOUTS = 65 { TIMEOUT_CRITICAL, TIMEOUT_MODERATE, TIMEOUT_NORMAL }; 66 67 private final ICarWatchdogServiceForSystem mWatchdogServiceForSystem; 68 private final CarWatchdogDaemonHelper mCarWatchdogDaemonHelper; 69 private final PackageInfoHandler mPackageInfoHandler; 70 private final Handler mMainHandler = new Handler(Looper.getMainLooper()); 71 private final Handler mServiceHandler = new Handler(getHandlerThread( 72 CarWatchdogService.class.getSimpleName()).getLooper()); 73 private final Object mLock = new Object(); 74 /* 75 * Keeps the list of car watchdog client according to timeout: 76 * key => timeout, value => ClientInfo list. 77 * The value of SparseArray is guarded by mLock. 78 */ 79 @GuardedBy("mLock") 80 private final SparseArray<ArrayList<ClientInfo>> mClientMap = new SparseArray<>(); 81 /* 82 * Keeps the map of car watchdog client being checked by CarWatchdogService according to 83 * timeout: key => timeout, value => ClientInfo map. 84 * The value is also a map: key => session id, value => ClientInfo. 85 */ 86 @GuardedBy("mLock") 87 private final SparseArray<SparseArray<ClientInfo>> mPingedClientMap = new SparseArray<>(); 88 /* 89 * Keeps whether client health checking is being performed according to timeout: 90 * key => timeout, value => boolean (whether client health checking is being performed). 91 * The value of SparseArray is guarded by mLock. 92 */ 93 @GuardedBy("mLock") 94 private final SparseArray<Boolean> mClientCheckInProgress = new SparseArray<>(); 95 @GuardedBy("mLock") 96 private final ArrayList<ClientInfo> mClientsNotResponding = new ArrayList<>(); 97 // mLastSessionId should only be accessed from the main thread. 98 @GuardedBy("mLock") 99 private int mLastSessionId; 100 @GuardedBy("mLock") 101 private final SparseBooleanArray mStoppedUser = new SparseBooleanArray(); 102 103 private long mOverriddenClientHealthCheckWindowMs = MISSING_INT_PROPERTY_VALUE; 104 WatchdogProcessHandler(ICarWatchdogServiceForSystem serviceImpl, CarWatchdogDaemonHelper daemonHelper, PackageInfoHandler packageInfoHandler)105 public WatchdogProcessHandler(ICarWatchdogServiceForSystem serviceImpl, 106 CarWatchdogDaemonHelper daemonHelper, PackageInfoHandler packageInfoHandler) { 107 mWatchdogServiceForSystem = serviceImpl; 108 mCarWatchdogDaemonHelper = daemonHelper; 109 mPackageInfoHandler = packageInfoHandler; 110 } 111 112 /** Initializes the handler. */ init()113 public void init() { 114 synchronized (mLock) { 115 for (int timeout : ALL_TIMEOUTS) { 116 mClientMap.put(timeout, new ArrayList<ClientInfo>()); 117 mPingedClientMap.put(timeout, new SparseArray<ClientInfo>()); 118 mClientCheckInProgress.put(timeout, false); 119 } 120 } 121 // Overridden timeout value must be greater than or equal to the maximum possible timeout 122 // value. Otherwise, clients will be pinged more frequently than the guaranteed timeout 123 // duration. 124 int clientHealthCheckWindowSec = SystemProperties.getInt( 125 PROPERTY_RO_CLIENT_HEALTHCHECK_INTERVAL, MISSING_INT_PROPERTY_VALUE); 126 if (clientHealthCheckWindowSec != MISSING_INT_PROPERTY_VALUE) { 127 mOverriddenClientHealthCheckWindowMs = Math.max(clientHealthCheckWindowSec * 1000L, 128 getTimeoutDurationMs(TIMEOUT_NORMAL)); 129 } 130 if (CarWatchdogService.DEBUG) { 131 Slogf.d(CarWatchdogService.TAG, "WatchdogProcessHandler is initialized"); 132 } 133 } 134 135 /** Dumps its state. */ 136 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)137 public void dump(IndentingPrintWriter writer) { 138 synchronized (mLock) { 139 writer.println("Registered clients"); 140 writer.increaseIndent(); 141 int count = 1; 142 for (int timeout : ALL_TIMEOUTS) { 143 ArrayList<ClientInfo> clients = mClientMap.get(timeout); 144 String timeoutStr = timeoutToString(timeout); 145 for (ClientInfo clientInfo : clients) { 146 writer.printf("client #%d: timeout = %s, pid = %d\n", count++, timeoutStr, 147 clientInfo.pid); 148 } 149 } 150 writer.printf("Stopped users: "); 151 int size = mStoppedUser.size(); 152 if (size > 0) { 153 writer.printf("%d", mStoppedUser.keyAt(0)); 154 for (int i = 1; i < size; i++) { 155 writer.printf(", %d", mStoppedUser.keyAt(i)); 156 } 157 writer.println(); 158 } else { 159 writer.println("none"); 160 } 161 writer.decreaseIndent(); 162 } 163 } 164 165 /** Dumps its state in proto format. */ 166 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dumpProto(ProtoOutputStream proto)167 public void dumpProto(ProtoOutputStream proto) { 168 synchronized (mLock) { 169 long systemHealthDumpToken = proto.start( 170 CarWatchdogDumpProto.SYSTEM_HEALTH_DUMP); 171 for (int timeout : ALL_TIMEOUTS) { 172 ArrayList<ClientInfo> clients = mClientMap.get(timeout); 173 for (ClientInfo clientInfo : clients) { 174 long registeredClientsToken = proto.start( 175 CarWatchdogDumpProto.SystemHealthDump.REGISTERED_CLIENTS); 176 proto.write(CarWatchdogDumpProto.RegisteredClient.PID, clientInfo.pid); 177 long userPackageInfoToken = proto.start( 178 CarWatchdogDumpProto.RegisteredClient.USER_PACKAGE_INFO); 179 proto.write(UserPackageInfo.USER_ID, clientInfo.getUserId()); 180 proto.write(UserPackageInfo.PACKAGE_NAME, clientInfo.packageName); 181 proto.end(userPackageInfoToken); 182 proto.write(CarWatchdogDumpProto.RegisteredClient.HEALTH_CHECK_TIMEOUT, 183 timeout + 1); 184 proto.end(registeredClientsToken); 185 } 186 } 187 for (int i = 0; i < mStoppedUser.size(); i++) { 188 proto.write(CarWatchdogDumpProto.SystemHealthDump.STOPPED_USERS, 189 mStoppedUser.keyAt(i)); 190 } 191 proto.end(systemHealthDumpToken); 192 } 193 } 194 195 /** Registers the client callback */ registerClient(ICarWatchdogServiceCallback client, @TimeoutLengthEnum int timeout)196 public void registerClient(ICarWatchdogServiceCallback client, @TimeoutLengthEnum int timeout) { 197 synchronized (mLock) { 198 ArrayList<ClientInfo> clients = mClientMap.get(timeout); 199 if (clients == null) { 200 Slogf.w(CarWatchdogService.TAG, "Cannot register the client: invalid timeout"); 201 return; 202 } 203 IBinder binder = client.asBinder(); 204 for (int i = 0; i < clients.size(); i++) { 205 ClientInfo clientInfo = clients.get(i); 206 if (binder == clientInfo.client.asBinder()) { 207 throw new IllegalStateException( 208 "Cannot register the client: the client(pid:" + clientInfo.pid 209 + ") has been already registered"); 210 } 211 } 212 int pid = Binder.getCallingPid(); 213 int callingUid = Binder.getCallingUid(); 214 ClientInfo clientInfo = new ClientInfo(client, pid, callingUid, timeout); 215 // PackageInfoHandler may need to retrieve the packageName from system server 216 // using a binder call. Thus, retrieving the packageName from PackageInfoHandler is 217 // posted on the looper and is resolved asynchronously. 218 mServiceHandler.post(() -> { 219 clientInfo.packageName = mPackageInfoHandler.getNamesForUids( 220 new int[]{callingUid}).get(callingUid, null); 221 }); 222 try { 223 clientInfo.linkToDeath(); 224 } catch (RemoteException e) { 225 Slogf.w(CarWatchdogService.TAG, 226 "Cannot register the client: linkToDeath to the client failed"); 227 return; 228 } 229 clients.add(clientInfo); 230 if (CarWatchdogService.DEBUG) { 231 Slogf.d(CarWatchdogService.TAG, "Registered client: %s", clientInfo); 232 } 233 } 234 } 235 236 /** Unregisters the previously registered client callback */ unregisterClient(ICarWatchdogServiceCallback client)237 public void unregisterClient(ICarWatchdogServiceCallback client) { 238 ClientInfo clientInfo; 239 synchronized (mLock) { 240 IBinder binder = client.asBinder(); 241 // Even if a client did not respond to the latest ping, CarWatchdogService should honor 242 // the unregister request at this point and remove it from all internal caches. 243 // Otherwise, the client might be killed even after unregistering. 244 Optional<ClientInfo> optionalClientInfo = removeFromClientMapsLocked(binder); 245 if (optionalClientInfo.isEmpty()) { 246 Slogf.w(CarWatchdogService.TAG, 247 "Cannot unregister the client: the client has not been registered before"); 248 return; 249 } 250 clientInfo = optionalClientInfo.get(); 251 for (int i = 0; i < mClientsNotResponding.size(); i++) { 252 ClientInfo notRespondingClientInfo = mClientsNotResponding.get(i); 253 if (binder == notRespondingClientInfo.client.asBinder()) { 254 mClientsNotResponding.remove(i); 255 break; 256 } 257 } 258 } 259 if (CarWatchdogService.DEBUG) { 260 Slogf.d(CarWatchdogService.TAG, "Unregistered client: %s", clientInfo); 261 } 262 } 263 264 @GuardedBy("mLock") removeFromClientMapsLocked(IBinder binder)265 private Optional<ClientInfo> removeFromClientMapsLocked(IBinder binder) { 266 for (int timeout : ALL_TIMEOUTS) { 267 ArrayList<ClientInfo> clients = mClientMap.get(timeout); 268 for (int i = 0; i < clients.size(); i++) { 269 ClientInfo clientInfo = clients.get(i); 270 if (binder != clientInfo.client.asBinder()) { 271 continue; 272 } 273 clientInfo.unlinkToDeath(); 274 clients.remove(i); 275 SparseArray<ClientInfo> pingedClients = mPingedClientMap.get(timeout); 276 if (pingedClients != null) { 277 pingedClients.remove(clientInfo.sessionId); 278 } 279 return Optional.of(clientInfo); 280 } 281 } 282 return Optional.empty(); 283 } 284 285 /** Tells the handler that the client is alive. */ tellClientAlive(ICarWatchdogServiceCallback client, int sessionId)286 public void tellClientAlive(ICarWatchdogServiceCallback client, int sessionId) { 287 synchronized (mLock) { 288 for (int timeout : ALL_TIMEOUTS) { 289 if (!mClientCheckInProgress.get(timeout)) { 290 continue; 291 } 292 SparseArray<ClientInfo> pingedClients = mPingedClientMap.get(timeout); 293 ClientInfo clientInfo = pingedClients.get(sessionId); 294 if (clientInfo != null && clientInfo.client.asBinder() == client.asBinder()) { 295 pingedClients.remove(sessionId); 296 return; 297 } 298 } 299 } 300 } 301 302 /** Updates the user stopped state */ updateUserState(@serIdInt int userId, boolean isStopped)303 public void updateUserState(@UserIdInt int userId, boolean isStopped) { 304 synchronized (mLock) { 305 if (isStopped) { 306 mStoppedUser.put(userId, true); 307 } else { 308 mStoppedUser.delete(userId); 309 } 310 } 311 } 312 313 /** Posts health check message */ postHealthCheckMessage(int sessionId)314 public void postHealthCheckMessage(int sessionId) { 315 mMainHandler.postAtFrontOfQueue(() -> doHealthCheck(sessionId)); 316 } 317 318 /** Returns the registered and alive client count. */ getClientCount(@imeoutLengthEnum int timeout)319 public int getClientCount(@TimeoutLengthEnum int timeout) { 320 synchronized (mLock) { 321 ArrayList<ClientInfo> clients = mClientMap.get(timeout); 322 return clients != null ? clients.size() : 0; 323 } 324 } 325 326 /** Resets pinged clients before health checking */ prepareHealthCheck()327 public void prepareHealthCheck() { 328 synchronized (mLock) { 329 for (int timeout : ALL_TIMEOUTS) { 330 SparseArray<ClientInfo> pingedClients = mPingedClientMap.get(timeout); 331 pingedClients.clear(); 332 } 333 } 334 } 335 336 /** 337 * Asynchronously fetches the AIDL VHAL pid from SystemServer. 338 * 339 * On fetching the AIDL VHAL pid, car watchdog daemon is updated via an async callback. 340 */ asyncFetchAidlVhalPid()341 public void asyncFetchAidlVhalPid() { 342 mServiceHandler.post(() -> { 343 Trace.beginSection("WatchdogProcessHandler.asyncFetchAidlVhalPid"); 344 int pid = CarServiceHelperWrapper.getInstance().fetchAidlVhalPid(); 345 if (pid < 0) { 346 Slogf.e(CarWatchdogService.TAG, "Failed to fetch AIDL VHAL pid from" 347 + " CarServiceHelperService"); 348 Trace.endSection(); 349 return; 350 } 351 try { 352 mCarWatchdogDaemonHelper.onAidlVhalPidFetched(pid); 353 } catch (RemoteException e) { 354 Slogf.e(CarWatchdogService.TAG, 355 "Failed to notify car watchdog daemon of the AIDL VHAL pid"); 356 } 357 Trace.endSection(); 358 }); 359 } 360 361 /** Enables/disables the watchdog daemon client health check process. */ controlProcessHealthCheck(boolean enable)362 void controlProcessHealthCheck(boolean enable) { 363 Trace.beginSection("WatchdogProcessHandler-healthCheckEnabled-" + enable); 364 try { 365 mCarWatchdogDaemonHelper.controlProcessHealthCheck(enable); 366 } catch (RemoteException e) { 367 Slogf.w(CarWatchdogService.TAG, 368 "Cannot enable/disable the car watchdog daemon health check process: %s", e); 369 } 370 Trace.endSection(); 371 } 372 onClientDeath(ICarWatchdogServiceCallback client, @TimeoutLengthEnum int timeout)373 private void onClientDeath(ICarWatchdogServiceCallback client, @TimeoutLengthEnum int timeout) { 374 synchronized (mLock) { 375 removeClientLocked(client.asBinder(), timeout); 376 } 377 } 378 doHealthCheck(int sessionId)379 private void doHealthCheck(int sessionId) { 380 // For critical clients, the response status are checked just before reporting to car 381 // watchdog daemon. For moderate and normal clients, the status are checked after allowed 382 // delay per timeout. 383 Trace.beginSection("WatchdogProcessHandler.doHealthCheck"); 384 analyzeClientResponse(TIMEOUT_CRITICAL); 385 reportHealthCheckResult(sessionId); 386 sendPingToClients(TIMEOUT_CRITICAL); 387 sendPingToClientsAndCheck(TIMEOUT_MODERATE); 388 sendPingToClientsAndCheck(TIMEOUT_NORMAL); 389 Trace.endSection(); 390 } 391 analyzeClientResponse(@imeoutLengthEnum int timeout)392 private void analyzeClientResponse(@TimeoutLengthEnum int timeout) { 393 // Clients which are not responding are stored in mClientsNotResponding, and will be dumped 394 // and killed at the next response of CarWatchdogService to car watchdog daemon. 395 Trace.beginSection("WatchdogProcessHandler.analyzeClientResponse"); 396 synchronized (mLock) { 397 SparseArray<ClientInfo> pingedClients = mPingedClientMap.get(timeout); 398 for (int i = 0; i < pingedClients.size(); i++) { 399 ClientInfo clientInfo = pingedClients.valueAt(i); 400 if (mStoppedUser.get(clientInfo.getUserId())) { 401 continue; 402 } 403 mClientsNotResponding.add(clientInfo); 404 removeClientLocked(clientInfo.client.asBinder(), timeout); 405 } 406 mClientCheckInProgress.setValueAt(timeout, false); 407 } 408 Trace.endSection(); 409 } 410 sendPingToClients(@imeoutLengthEnum int timeout)411 private void sendPingToClients(@TimeoutLengthEnum int timeout) { 412 Trace.beginSection("WatchdogProcessHandler.sendPingToClients"); 413 ArrayList<ClientInfo> clientsToCheck; 414 synchronized (mLock) { 415 SparseArray<ClientInfo> pingedClients = mPingedClientMap.get(timeout); 416 pingedClients.clear(); 417 clientsToCheck = new ArrayList<>(mClientMap.get(timeout)); 418 for (int i = 0; i < clientsToCheck.size(); i++) { 419 ClientInfo clientInfo = clientsToCheck.get(i); 420 if (mStoppedUser.get(clientInfo.getUserId())) { 421 continue; 422 } 423 int sessionId = getNewSessionId(); 424 clientInfo.sessionId = sessionId; 425 pingedClients.put(sessionId, clientInfo); 426 } 427 mClientCheckInProgress.setValueAt(timeout, true); 428 } 429 430 for (int i = 0; i < clientsToCheck.size(); i++) { 431 ClientInfo clientInfo = clientsToCheck.get(i); 432 try { 433 clientInfo.client.onCheckHealthStatus(clientInfo.sessionId, timeout); 434 } catch (RemoteException e) { 435 Slogf.w(CarWatchdogService.TAG, 436 "Sending a ping message to client(pid: %d) failed: %s", 437 clientInfo.pid, e); 438 synchronized (mLock) { 439 mPingedClientMap.get(timeout).remove(clientInfo.sessionId); 440 } 441 } 442 } 443 Trace.endSection(); 444 } 445 sendPingToClientsAndCheck(@imeoutLengthEnum int timeout)446 private void sendPingToClientsAndCheck(@TimeoutLengthEnum int timeout) { 447 synchronized (mLock) { 448 if (mClientCheckInProgress.get(timeout)) { 449 return; 450 } 451 } 452 sendPingToClients(timeout); 453 mMainHandler.postDelayed( 454 () -> analyzeClientResponse(timeout), getTimeoutDurationMs(timeout)); 455 } 456 getNewSessionId()457 private int getNewSessionId() { 458 synchronized (mLock) { 459 if (++mLastSessionId <= 0) { 460 mLastSessionId = 1; 461 } 462 return mLastSessionId; 463 } 464 } 465 466 @GuardedBy("mLock") removeClientLocked(IBinder clientBinder, @TimeoutLengthEnum int timeout)467 private void removeClientLocked(IBinder clientBinder, @TimeoutLengthEnum int timeout) { 468 ArrayList<ClientInfo> clients = mClientMap.get(timeout); 469 for (int i = 0; i < clients.size(); i++) { 470 ClientInfo clientInfo = clients.get(i); 471 if (clientBinder == clientInfo.client.asBinder()) { 472 clients.remove(i); 473 return; 474 } 475 } 476 } 477 reportHealthCheckResult(int sessionId)478 private void reportHealthCheckResult(int sessionId) { 479 Trace.beginSection("WatchdogProcessHandler.reportHealthCheckResult"); 480 List<ProcessIdentifier> clientsNotResponding; 481 ArrayList<ClientInfo> clientsToNotify; 482 synchronized (mLock) { 483 clientsNotResponding = toProcessIdentifierList(mClientsNotResponding); 484 clientsToNotify = new ArrayList<>(mClientsNotResponding); 485 mClientsNotResponding.clear(); 486 } 487 for (int i = 0; i < clientsToNotify.size(); i++) { 488 ClientInfo clientInfo = clientsToNotify.get(i); 489 try { 490 clientInfo.client.onPrepareProcessTermination(); 491 } catch (RemoteException e) { 492 Slogf.w(CarWatchdogService.TAG, 493 "Notifying onPrepareProcessTermination to client(pid: %d) failed: %s", 494 clientInfo.pid, e); 495 } 496 } 497 498 try { 499 mCarWatchdogDaemonHelper.tellCarWatchdogServiceAlive( 500 mWatchdogServiceForSystem, clientsNotResponding, sessionId); 501 } catch (RemoteException | RuntimeException e) { 502 Slogf.w(CarWatchdogService.TAG, 503 "Cannot respond to car watchdog daemon (sessionId=%d): %s", sessionId, e); 504 } 505 Trace.endSection(); 506 } 507 508 @NonNull toProcessIdentifierList( @onNull ArrayList<ClientInfo> clientInfos)509 private List<ProcessIdentifier> toProcessIdentifierList( 510 @NonNull ArrayList<ClientInfo> clientInfos) { 511 List<ProcessIdentifier> processIdentifiers = new ArrayList<>(clientInfos.size()); 512 for (int i = 0; i < clientInfos.size(); i++) { 513 ClientInfo clientInfo = clientInfos.get(i); 514 ProcessIdentifier processIdentifier = new ProcessIdentifier(); 515 processIdentifier.pid = clientInfo.pid; 516 processIdentifier.uid = clientInfo.uid; 517 processIdentifier.processName = clientInfo.packageName; 518 processIdentifier.startTimeMillis = clientInfo.startTimeMillis; 519 processIdentifiers.add(processIdentifier); 520 } 521 return processIdentifiers; 522 } 523 timeoutToString(@imeoutLengthEnum int timeout)524 private String timeoutToString(@TimeoutLengthEnum int timeout) { 525 switch (timeout) { 526 case TIMEOUT_CRITICAL: 527 return "critical"; 528 case TIMEOUT_MODERATE: 529 return "moderate"; 530 case TIMEOUT_NORMAL: 531 return "normal"; 532 default: 533 Slogf.w(CarWatchdogService.TAG, "Unknown timeout value"); 534 return "unknown"; 535 } 536 } 537 getTimeoutDurationMs(@imeoutLengthEnum int timeout)538 private long getTimeoutDurationMs(@TimeoutLengthEnum int timeout) { 539 if (mOverriddenClientHealthCheckWindowMs != MISSING_INT_PROPERTY_VALUE) { 540 return mOverriddenClientHealthCheckWindowMs; 541 } 542 switch (timeout) { 543 case TIMEOUT_CRITICAL: 544 return 3000L; 545 case TIMEOUT_MODERATE: 546 return 5000L; 547 case TIMEOUT_NORMAL: 548 return 10000L; 549 default: 550 Slogf.w(CarWatchdogService.TAG, "Unknown timeout value"); 551 return 10000L; 552 } 553 } 554 555 private final class ClientInfo implements IBinder.DeathRecipient { 556 public final ICarWatchdogServiceCallback client; 557 public final int pid; 558 public final long startTimeMillis; 559 public final int uid; 560 @TimeoutLengthEnum public final int timeout; 561 public volatile int sessionId; 562 public String packageName; 563 ClientInfo(ICarWatchdogServiceCallback client, int pid, int uid, @TimeoutLengthEnum int timeout)564 ClientInfo(ICarWatchdogServiceCallback client, int pid, int uid, 565 @TimeoutLengthEnum int timeout) { 566 this.client = client; 567 this.pid = pid; 568 // CarService doesn't have sepolicy access to read per-pid proc files, so it cannot 569 // fetch the pid's actual start time. When a client process registers with 570 // the CarService, it is safe to assume the process is still alive. So, populate 571 // elapsed real time and the consumer (CarServiceHelperService) of this data should 572 // verify that the actual start time is less than the reported start time. 573 this.startTimeMillis = SystemClock.elapsedRealtime(); 574 this.timeout = timeout; 575 this.uid = uid; 576 } 577 578 @Override binderDied()579 public void binderDied() { 580 Slogf.w(CarWatchdogService.TAG, "Client(pid: %d) died", pid); 581 unlinkToDeath(); 582 onClientDeath(client, timeout); 583 } 584 linkToDeath()585 private void linkToDeath() throws RemoteException { 586 client.asBinder().linkToDeath(this, 0); 587 } 588 getUserId()589 private int getUserId() { 590 return UserHandle.of(uid).getIdentifier(); 591 } 592 unlinkToDeath()593 private void unlinkToDeath() { 594 client.asBinder().unlinkToDeath(this, 0); 595 } 596 597 @Override toString()598 public String toString() { 599 return "ClientInfo{client=" + client + ", pid=" + pid 600 + ", startTimeMillis=" + startTimeMillis + ", uid=" + uid + ", timeout=" 601 + timeout + ", sessionId=" + sessionId + '}'; 602 } 603 } 604 } 605