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