1 /*
2  * Copyright (C) 2017 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;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.car.Car;
22 import android.car.diagnostic.CarDiagnosticEvent;
23 import android.car.diagnostic.CarDiagnosticManager;
24 import android.car.diagnostic.ICarDiagnostic;
25 import android.car.diagnostic.ICarDiagnosticEventListener;
26 import android.content.Context;
27 import android.os.IBinder;
28 import android.os.RemoteException;
29 import android.util.ArrayMap;
30 import android.util.Log;
31 
32 import com.android.car.Listeners.ClientWithRate;
33 import com.android.car.hal.DiagnosticHalService;
34 import com.android.car.hal.DiagnosticHalService.DiagnosticCapabilities;
35 import com.android.car.internal.CarPermission;
36 import com.android.internal.annotations.GuardedBy;
37 
38 import java.io.PrintWriter;
39 import java.util.Arrays;
40 import java.util.ConcurrentModificationException;
41 import java.util.HashMap;
42 import java.util.HashSet;
43 import java.util.LinkedList;
44 import java.util.List;
45 import java.util.Objects;
46 import java.util.Set;
47 import java.util.concurrent.locks.ReentrantLock;
48 
49 /** @hide */
50 public class CarDiagnosticService extends ICarDiagnostic.Stub
51         implements CarServiceBase, DiagnosticHalService.DiagnosticListener {
52     /** lock to access diagnostic structures */
53     private final ReentrantLock mDiagnosticLock = new ReentrantLock();
54     /** hold clients callback */
55     @GuardedBy("mDiagnosticLock")
56     private final LinkedList<DiagnosticClient> mClients = new LinkedList<>();
57 
58     /** key: diagnostic type. */
59     @GuardedBy("mDiagnosticLock")
60     private final HashMap<Integer, Listeners<DiagnosticClient>> mDiagnosticListeners =
61         new HashMap<>();
62 
63     /** the latest live frame data. */
64     @GuardedBy("mDiagnosticLock")
65     private final LiveFrameRecord mLiveFrameDiagnosticRecord = new LiveFrameRecord(mDiagnosticLock);
66 
67     /** the latest freeze frame data (key: DTC) */
68     @GuardedBy("mDiagnosticLock")
69     private final FreezeFrameRecord mFreezeFrameDiagnosticRecords = new FreezeFrameRecord(
70         mDiagnosticLock);
71 
72     private final DiagnosticHalService mDiagnosticHal;
73 
74     private final Context mContext;
75 
76     private final CarPermission mDiagnosticReadPermission;
77 
78     private final CarPermission mDiagnosticClearPermission;
79 
CarDiagnosticService(Context context, DiagnosticHalService diagnosticHal)80     public CarDiagnosticService(Context context, DiagnosticHalService diagnosticHal) {
81         mContext = context;
82         mDiagnosticHal = diagnosticHal;
83         mDiagnosticReadPermission = new CarPermission(mContext,
84                 Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL);
85         mDiagnosticClearPermission = new CarPermission(mContext,
86                 Car.PERMISSION_CAR_DIAGNOSTIC_CLEAR);
87     }
88 
89     @Override
init()90     public void init() {
91         mDiagnosticLock.lock();
92         try {
93             mDiagnosticHal.setDiagnosticListener(this);
94             setInitialLiveFrame();
95             setInitialFreezeFrames();
96         } finally {
97             mDiagnosticLock.unlock();
98         }
99     }
100 
101     @Nullable
setInitialLiveFrame()102     private CarDiagnosticEvent setInitialLiveFrame() {
103         CarDiagnosticEvent liveFrame = null;
104         if(mDiagnosticHal.getDiagnosticCapabilities().isLiveFrameSupported()) {
105             liveFrame = setRecentmostLiveFrame(mDiagnosticHal.getCurrentLiveFrame());
106         }
107         return liveFrame;
108     }
109 
setInitialFreezeFrames()110     private void setInitialFreezeFrames() {
111         if(mDiagnosticHal.getDiagnosticCapabilities().isFreezeFrameSupported() &&
112             mDiagnosticHal.getDiagnosticCapabilities().isFreezeFrameInfoSupported()) {
113             long[] timestamps = mDiagnosticHal.getFreezeFrameTimestamps();
114             if (timestamps != null) {
115                 for (long timestamp : timestamps) {
116                     setRecentmostFreezeFrame(mDiagnosticHal.getFreezeFrame(timestamp));
117                 }
118             }
119         }
120     }
121 
122     @Nullable
setRecentmostLiveFrame(final CarDiagnosticEvent event)123     private CarDiagnosticEvent setRecentmostLiveFrame(final CarDiagnosticEvent event) {
124         if (event != null) {
125             return mLiveFrameDiagnosticRecord.update(event.checkLiveFrame());
126         }
127         return null;
128     }
129 
130     @Nullable
setRecentmostFreezeFrame(final CarDiagnosticEvent event)131     private CarDiagnosticEvent setRecentmostFreezeFrame(final CarDiagnosticEvent event) {
132         if (event != null) {
133             return mFreezeFrameDiagnosticRecords.update(event.checkFreezeFrame());
134         }
135         return null;
136     }
137 
138     @Override
release()139     public void release() {
140         mDiagnosticLock.lock();
141         try {
142             mDiagnosticListeners.forEach(
143                     (Integer frameType, Listeners diagnosticListeners) ->
144                             diagnosticListeners.release());
145             mDiagnosticListeners.clear();
146             mLiveFrameDiagnosticRecord.disableIfNeeded();
147             mFreezeFrameDiagnosticRecords.disableIfNeeded();
148             mClients.clear();
149         } finally {
150             mDiagnosticLock.unlock();
151         }
152     }
153 
processDiagnosticData(List<CarDiagnosticEvent> events)154     private void processDiagnosticData(List<CarDiagnosticEvent> events) {
155         ArrayMap<CarDiagnosticService.DiagnosticClient, List<CarDiagnosticEvent>> eventsByClient =
156                 new ArrayMap<>();
157 
158         Listeners<DiagnosticClient> listeners = null;
159 
160         mDiagnosticLock.lock();
161         for (CarDiagnosticEvent event : events) {
162             if (event.isLiveFrame()) {
163                 // record recent-most live frame information
164                 setRecentmostLiveFrame(event);
165                 listeners = mDiagnosticListeners.get(CarDiagnosticManager.FRAME_TYPE_LIVE);
166             } else if (event.isFreezeFrame()) {
167                 setRecentmostFreezeFrame(event);
168                 listeners = mDiagnosticListeners.get(CarDiagnosticManager.FRAME_TYPE_FREEZE);
169             } else {
170                 Log.w(
171                         CarLog.TAG_DIAGNOSTIC,
172                         String.format("received unknown diagnostic event: %s", event));
173                 continue;
174             }
175 
176             if (null != listeners) {
177                 for (ClientWithRate<DiagnosticClient> clientWithRate : listeners.getClients()) {
178                     DiagnosticClient client = clientWithRate.getClient();
179                     List<CarDiagnosticEvent> clientEvents = eventsByClient.computeIfAbsent(client,
180                             (DiagnosticClient diagnosticClient) -> new LinkedList<>());
181                     clientEvents.add(event);
182                 }
183             }
184         }
185         mDiagnosticLock.unlock();
186 
187         for (ArrayMap.Entry<CarDiagnosticService.DiagnosticClient, List<CarDiagnosticEvent>> entry :
188                 eventsByClient.entrySet()) {
189             CarDiagnosticService.DiagnosticClient client = entry.getKey();
190             List<CarDiagnosticEvent> clientEvents = entry.getValue();
191 
192             client.dispatchDiagnosticUpdate(clientEvents);
193         }
194     }
195 
196     /** Received diagnostic data from car. */
197     @Override
onDiagnosticEvents(List<CarDiagnosticEvent> events)198     public void onDiagnosticEvents(List<CarDiagnosticEvent> events) {
199         processDiagnosticData(events);
200     }
201 
202     @Override
registerOrUpdateDiagnosticListener(int frameType, int rate, ICarDiagnosticEventListener listener)203     public boolean registerOrUpdateDiagnosticListener(int frameType, int rate,
204                 ICarDiagnosticEventListener listener) {
205         boolean shouldStartDiagnostics = false;
206         CarDiagnosticService.DiagnosticClient diagnosticClient = null;
207         Integer oldRate = null;
208         Listeners<DiagnosticClient> diagnosticListeners = null;
209         mDiagnosticLock.lock();
210         try {
211             mDiagnosticReadPermission.assertGranted();
212             diagnosticClient = findDiagnosticClientLocked(listener);
213             Listeners.ClientWithRate<DiagnosticClient> diagnosticClientWithRate = null;
214             if (diagnosticClient == null) {
215                 diagnosticClient = new DiagnosticClient(listener);
216                 try {
217                     listener.asBinder().linkToDeath(diagnosticClient, 0);
218                 } catch (RemoteException e) {
219                     Log.w(
220                             CarLog.TAG_DIAGNOSTIC,
221                             String.format(
222                                     "received RemoteException trying to register listener for %s",
223                                     frameType));
224                     return false;
225                 }
226                 mClients.add(diagnosticClient);
227             }
228             diagnosticListeners = mDiagnosticListeners.get(frameType);
229             if (diagnosticListeners == null) {
230                 diagnosticListeners = new Listeners<>(rate);
231                 mDiagnosticListeners.put(frameType, diagnosticListeners);
232                 shouldStartDiagnostics = true;
233             } else {
234                 oldRate = diagnosticListeners.getRate();
235                 diagnosticClientWithRate =
236                         diagnosticListeners.findClientWithRate(diagnosticClient);
237             }
238             if (diagnosticClientWithRate == null) {
239                 diagnosticClientWithRate =
240                         new ClientWithRate<>(diagnosticClient, rate);
241                 diagnosticListeners.addClientWithRate(diagnosticClientWithRate);
242             } else {
243                 diagnosticClientWithRate.setRate(rate);
244             }
245             if (diagnosticListeners.getRate() > rate) {
246                 diagnosticListeners.setRate(rate);
247                 shouldStartDiagnostics = true;
248             }
249             diagnosticClient.addDiagnostic(frameType);
250         } finally {
251             mDiagnosticLock.unlock();
252         }
253         Log.i(
254                 CarLog.TAG_DIAGNOSTIC,
255                 String.format(
256                         "shouldStartDiagnostics = %s for %s at rate %d",
257                         shouldStartDiagnostics, frameType, rate));
258         // start diagnostic outside lock as it can take time.
259         if (shouldStartDiagnostics) {
260             if (!startDiagnostic(frameType, rate)) {
261                 // failed. so remove from active diagnostic list.
262                 Log.w(CarLog.TAG_DIAGNOSTIC, "startDiagnostic failed");
263                 mDiagnosticLock.lock();
264                 try {
265                     diagnosticClient.removeDiagnostic(frameType);
266                     if (oldRate != null) {
267                         diagnosticListeners.setRate(oldRate);
268                     } else {
269                         mDiagnosticListeners.remove(frameType);
270                     }
271                 } finally {
272                     mDiagnosticLock.unlock();
273                 }
274                 return false;
275             }
276         }
277         return true;
278     }
279 
startDiagnostic(int frameType, int rate)280     private boolean startDiagnostic(int frameType, int rate) {
281         Log.i(CarLog.TAG_DIAGNOSTIC, String.format("starting diagnostic %s at rate %d",
282                 frameType, rate));
283         DiagnosticHalService diagnosticHal = getDiagnosticHal();
284         if (diagnosticHal != null) {
285             if (!diagnosticHal.isReady()) {
286                 Log.w(CarLog.TAG_DIAGNOSTIC, "diagnosticHal not ready");
287                 return false;
288             }
289             switch (frameType) {
290                 case CarDiagnosticManager.FRAME_TYPE_LIVE:
291                     if (mLiveFrameDiagnosticRecord.isEnabled()) {
292                         return true;
293                     }
294                     if (diagnosticHal.requestDiagnosticStart(CarDiagnosticManager.FRAME_TYPE_LIVE,
295                             rate)) {
296                         mLiveFrameDiagnosticRecord.enable();
297                         return true;
298                     }
299                     break;
300                 case CarDiagnosticManager.FRAME_TYPE_FREEZE:
301                     if (mFreezeFrameDiagnosticRecords.isEnabled()) {
302                         return true;
303                     }
304                     if (diagnosticHal.requestDiagnosticStart(CarDiagnosticManager.FRAME_TYPE_FREEZE,
305                             rate)) {
306                         mFreezeFrameDiagnosticRecords.enable();
307                         return true;
308                     }
309                     break;
310             }
311         }
312         return false;
313     }
314 
315     @Override
unregisterDiagnosticListener( int frameType, ICarDiagnosticEventListener listener)316     public void unregisterDiagnosticListener(
317             int frameType, ICarDiagnosticEventListener listener) {
318         boolean shouldStopDiagnostic = false;
319         boolean shouldRestartDiagnostic = false;
320         int newRate = 0;
321         mDiagnosticLock.lock();
322         try {
323             DiagnosticClient diagnosticClient = findDiagnosticClientLocked(listener);
324             if (diagnosticClient == null) {
325                 Log.i(
326                         CarLog.TAG_DIAGNOSTIC,
327                         String.format(
328                                 "trying to unregister diagnostic client %s for %s which is not registered",
329                                 listener, frameType));
330                 // never registered or already unregistered.
331                 return;
332             }
333             diagnosticClient.removeDiagnostic(frameType);
334             if (diagnosticClient.getNumberOfActiveDiagnostic() == 0) {
335                 diagnosticClient.release();
336                 mClients.remove(diagnosticClient);
337             }
338             Listeners<DiagnosticClient> diagnosticListeners = mDiagnosticListeners.get(frameType);
339             if (diagnosticListeners == null) {
340                 // diagnostic not active
341                 return;
342             }
343             ClientWithRate<DiagnosticClient> clientWithRate =
344                     diagnosticListeners.findClientWithRate(diagnosticClient);
345             if (clientWithRate == null) {
346                 return;
347             }
348             diagnosticListeners.removeClientWithRate(clientWithRate);
349             if (diagnosticListeners.getNumberOfClients() == 0) {
350                 shouldStopDiagnostic = true;
351                 mDiagnosticListeners.remove(frameType);
352             } else if (diagnosticListeners.updateRate()) { // rate changed
353                 newRate = diagnosticListeners.getRate();
354                 shouldRestartDiagnostic = true;
355             }
356         } finally {
357             mDiagnosticLock.unlock();
358         }
359         Log.i(
360                 CarLog.TAG_DIAGNOSTIC,
361                 String.format(
362                         "shouldStopDiagnostic = %s, shouldRestartDiagnostic = %s for type %s",
363                         shouldStopDiagnostic, shouldRestartDiagnostic, frameType));
364         if (shouldStopDiagnostic) {
365             stopDiagnostic(frameType);
366         } else if (shouldRestartDiagnostic) {
367             startDiagnostic(frameType, newRate);
368         }
369     }
370 
stopDiagnostic(int frameType)371     private void stopDiagnostic(int frameType) {
372         DiagnosticHalService diagnosticHal = getDiagnosticHal();
373         if (diagnosticHal == null || !diagnosticHal.isReady()) {
374             Log.w(CarLog.TAG_DIAGNOSTIC, "diagnosticHal not ready");
375             return;
376         }
377         switch (frameType) {
378             case CarDiagnosticManager.FRAME_TYPE_LIVE:
379                 if (mLiveFrameDiagnosticRecord.disableIfNeeded())
380                     diagnosticHal.requestDiagnosticStop(CarDiagnosticManager.FRAME_TYPE_LIVE);
381                 break;
382             case CarDiagnosticManager.FRAME_TYPE_FREEZE:
383                 if (mFreezeFrameDiagnosticRecords.disableIfNeeded())
384                     diagnosticHal.requestDiagnosticStop(CarDiagnosticManager.FRAME_TYPE_FREEZE);
385                 break;
386         }
387     }
388 
getDiagnosticHal()389     private DiagnosticHalService getDiagnosticHal() {
390         return mDiagnosticHal;
391     }
392 
393     // Expose DiagnosticCapabilities
isLiveFrameSupported()394     public boolean isLiveFrameSupported() {
395         return getDiagnosticHal().getDiagnosticCapabilities().isLiveFrameSupported();
396     }
397 
isFreezeFrameNotificationSupported()398     public boolean isFreezeFrameNotificationSupported() {
399         return getDiagnosticHal().getDiagnosticCapabilities().isFreezeFrameSupported();
400     }
401 
isGetFreezeFrameSupported()402     public boolean isGetFreezeFrameSupported() {
403         DiagnosticCapabilities diagnosticCapabilities =
404                 getDiagnosticHal().getDiagnosticCapabilities();
405         return diagnosticCapabilities.isFreezeFrameInfoSupported() &&
406                 diagnosticCapabilities.isFreezeFrameSupported();
407     }
408 
isClearFreezeFramesSupported()409     public boolean isClearFreezeFramesSupported() {
410         DiagnosticCapabilities diagnosticCapabilities =
411             getDiagnosticHal().getDiagnosticCapabilities();
412         return diagnosticCapabilities.isFreezeFrameClearSupported() &&
413             diagnosticCapabilities.isFreezeFrameSupported();
414     }
415 
isSelectiveClearFreezeFramesSupported()416     public boolean isSelectiveClearFreezeFramesSupported() {
417         DiagnosticCapabilities diagnosticCapabilities =
418             getDiagnosticHal().getDiagnosticCapabilities();
419         return isClearFreezeFramesSupported() &&
420                 diagnosticCapabilities.isSelectiveClearFreezeFramesSupported();
421     }
422 
423     // ICarDiagnostic implementations
424 
425     @Override
getLatestLiveFrame()426     public CarDiagnosticEvent getLatestLiveFrame() {
427         mLiveFrameDiagnosticRecord.lock();
428         CarDiagnosticEvent liveFrame = mLiveFrameDiagnosticRecord.getLastEvent();
429         mLiveFrameDiagnosticRecord.unlock();
430         return liveFrame;
431     }
432 
433     @Override
getFreezeFrameTimestamps()434     public long[] getFreezeFrameTimestamps() {
435         mFreezeFrameDiagnosticRecords.lock();
436         long[] timestamps = mFreezeFrameDiagnosticRecords.getFreezeFrameTimestamps();
437         mFreezeFrameDiagnosticRecords.unlock();
438         return timestamps;
439     }
440 
441     @Override
442     @Nullable
getFreezeFrame(long timestamp)443     public CarDiagnosticEvent getFreezeFrame(long timestamp) {
444         mFreezeFrameDiagnosticRecords.lock();
445         CarDiagnosticEvent freezeFrame = mFreezeFrameDiagnosticRecords.getEvent(timestamp);
446         mFreezeFrameDiagnosticRecords.unlock();
447         return freezeFrame;
448     }
449 
450     @Override
clearFreezeFrames(long... timestamps)451     public boolean clearFreezeFrames(long... timestamps) {
452         mDiagnosticClearPermission.assertGranted();
453         if (!isClearFreezeFramesSupported())
454             return false;
455         if (timestamps != null && timestamps.length != 0) {
456             if (!isSelectiveClearFreezeFramesSupported()) {
457                 return false;
458             }
459         }
460         mFreezeFrameDiagnosticRecords.lock();
461         mDiagnosticHal.clearFreezeFrames(timestamps);
462         mFreezeFrameDiagnosticRecords.clearEvents();
463         mFreezeFrameDiagnosticRecords.unlock();
464         return true;
465     }
466 
467     /**
468      * Find DiagnosticClient from client list and return it. This should be called with mClients
469      * locked.
470      *
471      * @param listener
472      * @return null if not found.
473      */
474     @GuardedBy("mDiagnosticLock")
findDiagnosticClientLocked( ICarDiagnosticEventListener listener)475     private CarDiagnosticService.DiagnosticClient findDiagnosticClientLocked(
476             ICarDiagnosticEventListener listener) {
477         IBinder binder = listener.asBinder();
478         for (DiagnosticClient diagnosticClient : mClients) {
479             if (diagnosticClient.isHoldingListenerBinder(binder)) {
480                 return diagnosticClient;
481             }
482         }
483         return null;
484     }
485 
removeClient(DiagnosticClient diagnosticClient)486     private void removeClient(DiagnosticClient diagnosticClient) {
487         mDiagnosticLock.lock();
488         try {
489             for (int diagnostic : diagnosticClient.getDiagnosticArray()) {
490                 unregisterDiagnosticListener(
491                         diagnostic, diagnosticClient.getICarDiagnosticEventListener());
492             }
493             mClients.remove(diagnosticClient);
494         } finally {
495             mDiagnosticLock.unlock();
496         }
497     }
498 
499     /** internal instance for pending client request */
500     private class DiagnosticClient implements Listeners.IListener {
501         /** callback for diagnostic events */
502         private final ICarDiagnosticEventListener mListener;
503 
504         private final Set<Integer> mActiveDiagnostics = new HashSet<>();
505 
506         /** when false, it is already released */
507         private volatile boolean mActive = true;
508 
DiagnosticClient(ICarDiagnosticEventListener listener)509         DiagnosticClient(ICarDiagnosticEventListener listener) {
510             this.mListener = listener;
511         }
512 
513         @Override
equals(Object o)514         public boolean equals(Object o) {
515             return o instanceof DiagnosticClient
516                 && mListener.asBinder()
517                 == ((DiagnosticClient) o).mListener.asBinder();
518         }
519 
isHoldingListenerBinder(IBinder listenerBinder)520         boolean isHoldingListenerBinder(IBinder listenerBinder) {
521             return mListener.asBinder() == listenerBinder;
522         }
523 
addDiagnostic(int frameType)524         void addDiagnostic(int frameType) {
525             mActiveDiagnostics.add(frameType);
526         }
527 
removeDiagnostic(int frameType)528         void removeDiagnostic(int frameType) {
529             mActiveDiagnostics.remove(frameType);
530         }
531 
getNumberOfActiveDiagnostic()532         int getNumberOfActiveDiagnostic() {
533             return mActiveDiagnostics.size();
534         }
535 
getDiagnosticArray()536         int[] getDiagnosticArray() {
537             return mActiveDiagnostics.stream().mapToInt(Integer::intValue).toArray();
538         }
539 
getICarDiagnosticEventListener()540         ICarDiagnosticEventListener getICarDiagnosticEventListener() {
541             return mListener;
542         }
543 
544         /** Client dead. should remove all diagnostic requests from client */
545         @Override
binderDied()546         public void binderDied() {
547             mListener.asBinder().unlinkToDeath(this, 0);
548             removeClient(this);
549         }
550 
dispatchDiagnosticUpdate(List<CarDiagnosticEvent> events)551         void dispatchDiagnosticUpdate(List<CarDiagnosticEvent> events) {
552             if (events.size() != 0 && mActive) {
553                 try {
554                     mListener.onDiagnosticEvents(events);
555                 } catch (RemoteException e) {
556                     //ignore. crash will be handled by death handler
557                 }
558             }
559         }
560 
561         @Override
release()562         public void release() {
563             if (mActive) {
564                 mListener.asBinder().unlinkToDeath(this, 0);
565                 mActiveDiagnostics.clear();
566                 mActive = false;
567             }
568         }
569     }
570 
571     private static abstract class DiagnosticRecord {
572         private final ReentrantLock mLock;
573         protected boolean mEnabled = false;
574 
DiagnosticRecord(ReentrantLock lock)575         DiagnosticRecord(ReentrantLock lock) {
576             mLock = lock;
577         }
578 
lock()579         void lock() {
580             mLock.lock();
581         }
582 
unlock()583         void unlock() {
584             mLock.unlock();
585         }
586 
isEnabled()587         boolean isEnabled() {
588             return mEnabled;
589         }
590 
enable()591         void enable() {
592             mEnabled = true;
593         }
594 
disableIfNeeded()595         abstract boolean disableIfNeeded();
update(CarDiagnosticEvent newEvent)596         abstract CarDiagnosticEvent update(CarDiagnosticEvent newEvent);
597     }
598 
599     private static class LiveFrameRecord extends DiagnosticRecord {
600         /** Store the most recent live-frame. */
601         CarDiagnosticEvent mLastEvent = null;
602 
LiveFrameRecord(ReentrantLock lock)603         LiveFrameRecord(ReentrantLock lock) {
604             super(lock);
605         }
606 
607         @Override
disableIfNeeded()608         boolean disableIfNeeded() {
609             if (!mEnabled) return false;
610             mEnabled = false;
611             mLastEvent = null;
612             return true;
613         }
614 
615         @Override
update(@onNull CarDiagnosticEvent newEvent)616         CarDiagnosticEvent update(@NonNull CarDiagnosticEvent newEvent) {
617             Objects.requireNonNull(newEvent);
618             if((null == mLastEvent) || mLastEvent.isEarlierThan(newEvent))
619                 mLastEvent = newEvent;
620             return mLastEvent;
621         }
622 
getLastEvent()623         CarDiagnosticEvent getLastEvent() {
624             return mLastEvent;
625         }
626     }
627 
628     private static class FreezeFrameRecord extends DiagnosticRecord {
629         /** Store the timestamp --> freeze frame mapping. */
630         HashMap<Long, CarDiagnosticEvent> mEvents = new HashMap<>();
631 
FreezeFrameRecord(ReentrantLock lock)632         FreezeFrameRecord(ReentrantLock lock) {
633             super(lock);
634         }
635 
636         @Override
disableIfNeeded()637         boolean disableIfNeeded() {
638             if (!mEnabled) return false;
639             mEnabled = false;
640             clearEvents();
641             return true;
642         }
643 
clearEvents()644         void clearEvents() {
645             mEvents.clear();
646         }
647 
648         @Override
update(@onNull CarDiagnosticEvent newEvent)649         CarDiagnosticEvent update(@NonNull CarDiagnosticEvent newEvent) {
650             mEvents.put(newEvent.timestamp, newEvent);
651             return newEvent;
652         }
653 
getFreezeFrameTimestamps()654         long[] getFreezeFrameTimestamps() {
655             return mEvents.keySet().stream().mapToLong(Long::longValue).toArray();
656         }
657 
getEvent(long timestamp)658         CarDiagnosticEvent getEvent(long timestamp) {
659             return mEvents.get(timestamp);
660         }
661 
getEvents()662         Iterable<CarDiagnosticEvent> getEvents() {
663             return mEvents.values();
664         }
665     }
666 
667     @Override
dump(PrintWriter writer)668     public void dump(PrintWriter writer) {
669         writer.println("*CarDiagnosticService*");
670         writer.println("**last events for diagnostics**");
671         if (null != mLiveFrameDiagnosticRecord.getLastEvent()) {
672             writer.println("last live frame event: ");
673             writer.println(mLiveFrameDiagnosticRecord.getLastEvent());
674         }
675         writer.println("freeze frame events: ");
676         mFreezeFrameDiagnosticRecords.getEvents().forEach(writer::println);
677         writer.println("**clients**");
678         try {
679             for (DiagnosticClient client : mClients) {
680                 if (client != null) {
681                     try {
682                         writer.println(
683                                 "binder:"
684                                         + client.mListener
685                                         + " active diagnostics:"
686                                         + Arrays.toString(client.getDiagnosticArray()));
687                     } catch (ConcurrentModificationException e) {
688                         writer.println("concurrent modification happened");
689                     }
690                 } else {
691                     writer.println("null client");
692                 }
693             }
694         } catch (ConcurrentModificationException e) {
695             writer.println("concurrent modification happened");
696         }
697         writer.println("**diagnostic listeners**");
698         try {
699             for (int diagnostic : mDiagnosticListeners.keySet()) {
700                 Listeners diagnosticListeners = mDiagnosticListeners.get(diagnostic);
701                 if (diagnosticListeners != null) {
702                     writer.println(
703                             " Diagnostic:"
704                                     + diagnostic
705                                     + " num client:"
706                                     + diagnosticListeners.getNumberOfClients()
707                                     + " rate:"
708                                     + diagnosticListeners.getRate());
709                 }
710             }
711         } catch (ConcurrentModificationException e) {
712             writer.println("concurrent modification happened");
713         }
714     }
715 }
716