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