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