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.IndentingPrintWriter;
31 import android.util.Slog;
32 
33 import com.android.car.Listeners.ClientWithRate;
34 import com.android.car.hal.DiagnosticHalService;
35 import com.android.car.hal.DiagnosticHalService.DiagnosticCapabilities;
36 import com.android.car.internal.CarPermission;
37 import com.android.internal.annotations.GuardedBy;
38 
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                 Slog.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                     Slog.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         Slog.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                 Slog.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         Slog.i(CarLog.TAG_DIAGNOSTIC, "starting diagnostic " + frameType + " at rate " + rate);
282         DiagnosticHalService diagnosticHal = getDiagnosticHal();
283         if (diagnosticHal != null) {
284             if (!diagnosticHal.isReady()) {
285                 Slog.w(CarLog.TAG_DIAGNOSTIC, "diagnosticHal not ready");
286                 return false;
287             }
288             switch (frameType) {
289                 case CarDiagnosticManager.FRAME_TYPE_LIVE:
290                     if (mLiveFrameDiagnosticRecord.isEnabled()) {
291                         return true;
292                     }
293                     if (diagnosticHal.requestDiagnosticStart(CarDiagnosticManager.FRAME_TYPE_LIVE,
294                             rate)) {
295                         mLiveFrameDiagnosticRecord.enable();
296                         return true;
297                     }
298                     break;
299                 case CarDiagnosticManager.FRAME_TYPE_FREEZE:
300                     if (mFreezeFrameDiagnosticRecords.isEnabled()) {
301                         return true;
302                     }
303                     if (diagnosticHal.requestDiagnosticStart(CarDiagnosticManager.FRAME_TYPE_FREEZE,
304                             rate)) {
305                         mFreezeFrameDiagnosticRecords.enable();
306                         return true;
307                     }
308                     break;
309             }
310         }
311         return false;
312     }
313 
314     @Override
unregisterDiagnosticListener( int frameType, ICarDiagnosticEventListener listener)315     public void unregisterDiagnosticListener(
316             int frameType, ICarDiagnosticEventListener listener) {
317         boolean shouldStopDiagnostic = false;
318         boolean shouldRestartDiagnostic = false;
319         int newRate = 0;
320         mDiagnosticLock.lock();
321         try {
322             DiagnosticClient diagnosticClient = findDiagnosticClientLocked(listener);
323             if (diagnosticClient == null) {
324                 Slog.i(
325                         CarLog.TAG_DIAGNOSTIC,
326                         String.format(
327                                 "trying to unregister diagnostic client %s for %s which is not registered",
328                                 listener, frameType));
329                 // never registered or already unregistered.
330                 return;
331             }
332             diagnosticClient.removeDiagnostic(frameType);
333             if (diagnosticClient.getNumberOfActiveDiagnostic() == 0) {
334                 diagnosticClient.release();
335                 mClients.remove(diagnosticClient);
336             }
337             Listeners<DiagnosticClient> diagnosticListeners = mDiagnosticListeners.get(frameType);
338             if (diagnosticListeners == null) {
339                 // diagnostic not active
340                 return;
341             }
342             ClientWithRate<DiagnosticClient> clientWithRate =
343                     diagnosticListeners.findClientWithRate(diagnosticClient);
344             if (clientWithRate == null) {
345                 return;
346             }
347             diagnosticListeners.removeClientWithRate(clientWithRate);
348             if (diagnosticListeners.getNumberOfClients() == 0) {
349                 shouldStopDiagnostic = true;
350                 mDiagnosticListeners.remove(frameType);
351             } else if (diagnosticListeners.updateRate()) { // rate changed
352                 newRate = diagnosticListeners.getRate();
353                 shouldRestartDiagnostic = true;
354             }
355         } finally {
356             mDiagnosticLock.unlock();
357         }
358         Slog.i(
359                 CarLog.TAG_DIAGNOSTIC,
360                 String.format(
361                         "shouldStopDiagnostic = %s, shouldRestartDiagnostic = %s for type %s",
362                         shouldStopDiagnostic, shouldRestartDiagnostic, frameType));
363         if (shouldStopDiagnostic) {
364             stopDiagnostic(frameType);
365         } else if (shouldRestartDiagnostic) {
366             startDiagnostic(frameType, newRate);
367         }
368     }
369 
stopDiagnostic(int frameType)370     private void stopDiagnostic(int frameType) {
371         DiagnosticHalService diagnosticHal = getDiagnosticHal();
372         if (diagnosticHal == null || !diagnosticHal.isReady()) {
373             Slog.w(CarLog.TAG_DIAGNOSTIC, "diagnosticHal not ready");
374             return;
375         }
376         switch (frameType) {
377             case CarDiagnosticManager.FRAME_TYPE_LIVE:
378                 if (mLiveFrameDiagnosticRecord.disableIfNeeded())
379                     diagnosticHal.requestDiagnosticStop(CarDiagnosticManager.FRAME_TYPE_LIVE);
380                 break;
381             case CarDiagnosticManager.FRAME_TYPE_FREEZE:
382                 if (mFreezeFrameDiagnosticRecords.disableIfNeeded())
383                     diagnosticHal.requestDiagnosticStop(CarDiagnosticManager.FRAME_TYPE_FREEZE);
384                 break;
385         }
386     }
387 
getDiagnosticHal()388     private DiagnosticHalService getDiagnosticHal() {
389         return mDiagnosticHal;
390     }
391 
392     // Expose DiagnosticCapabilities
isLiveFrameSupported()393     public boolean isLiveFrameSupported() {
394         return getDiagnosticHal().getDiagnosticCapabilities().isLiveFrameSupported();
395     }
396 
isFreezeFrameNotificationSupported()397     public boolean isFreezeFrameNotificationSupported() {
398         return getDiagnosticHal().getDiagnosticCapabilities().isFreezeFrameSupported();
399     }
400 
isGetFreezeFrameSupported()401     public boolean isGetFreezeFrameSupported() {
402         DiagnosticCapabilities diagnosticCapabilities =
403                 getDiagnosticHal().getDiagnosticCapabilities();
404         return diagnosticCapabilities.isFreezeFrameInfoSupported() &&
405                 diagnosticCapabilities.isFreezeFrameSupported();
406     }
407 
isClearFreezeFramesSupported()408     public boolean isClearFreezeFramesSupported() {
409         DiagnosticCapabilities diagnosticCapabilities =
410             getDiagnosticHal().getDiagnosticCapabilities();
411         return diagnosticCapabilities.isFreezeFrameClearSupported() &&
412             diagnosticCapabilities.isFreezeFrameSupported();
413     }
414 
isSelectiveClearFreezeFramesSupported()415     public boolean isSelectiveClearFreezeFramesSupported() {
416         DiagnosticCapabilities diagnosticCapabilities =
417             getDiagnosticHal().getDiagnosticCapabilities();
418         return isClearFreezeFramesSupported() &&
419                 diagnosticCapabilities.isSelectiveClearFreezeFramesSupported();
420     }
421 
422     // ICarDiagnostic implementations
423 
424     @Override
getLatestLiveFrame()425     public CarDiagnosticEvent getLatestLiveFrame() {
426         mLiveFrameDiagnosticRecord.lock();
427         CarDiagnosticEvent liveFrame = mLiveFrameDiagnosticRecord.getLastEvent();
428         mLiveFrameDiagnosticRecord.unlock();
429         return liveFrame;
430     }
431 
432     @Override
getFreezeFrameTimestamps()433     public long[] getFreezeFrameTimestamps() {
434         mFreezeFrameDiagnosticRecords.lock();
435         long[] timestamps = mFreezeFrameDiagnosticRecords.getFreezeFrameTimestamps();
436         mFreezeFrameDiagnosticRecords.unlock();
437         return timestamps;
438     }
439 
440     @Override
441     @Nullable
getFreezeFrame(long timestamp)442     public CarDiagnosticEvent getFreezeFrame(long timestamp) {
443         mFreezeFrameDiagnosticRecords.lock();
444         CarDiagnosticEvent freezeFrame = mFreezeFrameDiagnosticRecords.getEvent(timestamp);
445         mFreezeFrameDiagnosticRecords.unlock();
446         return freezeFrame;
447     }
448 
449     @Override
clearFreezeFrames(long... timestamps)450     public boolean clearFreezeFrames(long... timestamps) {
451         mDiagnosticClearPermission.assertGranted();
452         if (!isClearFreezeFramesSupported())
453             return false;
454         if (timestamps != null && timestamps.length != 0) {
455             if (!isSelectiveClearFreezeFramesSupported()) {
456                 return false;
457             }
458         }
459         mFreezeFrameDiagnosticRecords.lock();
460         mDiagnosticHal.clearFreezeFrames(timestamps);
461         mFreezeFrameDiagnosticRecords.clearEvents();
462         mFreezeFrameDiagnosticRecords.unlock();
463         return true;
464     }
465 
466     /**
467      * Find DiagnosticClient from client list and return it. This should be called with mClients
468      * locked.
469      *
470      * @param listener
471      * @return null if not found.
472      */
473     @GuardedBy("mDiagnosticLock")
findDiagnosticClientLocked( ICarDiagnosticEventListener listener)474     private CarDiagnosticService.DiagnosticClient findDiagnosticClientLocked(
475             ICarDiagnosticEventListener listener) {
476         IBinder binder = listener.asBinder();
477         for (DiagnosticClient diagnosticClient : mClients) {
478             if (diagnosticClient.isHoldingListenerBinder(binder)) {
479                 return diagnosticClient;
480             }
481         }
482         return null;
483     }
484 
removeClient(DiagnosticClient diagnosticClient)485     private void removeClient(DiagnosticClient diagnosticClient) {
486         mDiagnosticLock.lock();
487         try {
488             for (int diagnostic : diagnosticClient.getDiagnosticArray()) {
489                 unregisterDiagnosticListener(
490                         diagnostic, diagnosticClient.getICarDiagnosticEventListener());
491             }
492             mClients.remove(diagnosticClient);
493         } finally {
494             mDiagnosticLock.unlock();
495         }
496     }
497 
498     /** internal instance for pending client request */
499     private class DiagnosticClient implements Listeners.IListener {
500         /** callback for diagnostic events */
501         private final ICarDiagnosticEventListener mListener;
502 
503         private final Set<Integer> mActiveDiagnostics = new HashSet<>();
504 
505         /** when false, it is already released */
506         private volatile boolean mActive = true;
507 
DiagnosticClient(ICarDiagnosticEventListener listener)508         DiagnosticClient(ICarDiagnosticEventListener listener) {
509             this.mListener = listener;
510         }
511 
512         @Override
equals(Object o)513         public boolean equals(Object o) {
514             return o instanceof DiagnosticClient
515                 && mListener.asBinder()
516                 == ((DiagnosticClient) o).mListener.asBinder();
517         }
518 
isHoldingListenerBinder(IBinder listenerBinder)519         boolean isHoldingListenerBinder(IBinder listenerBinder) {
520             return mListener.asBinder() == listenerBinder;
521         }
522 
addDiagnostic(int frameType)523         void addDiagnostic(int frameType) {
524             mActiveDiagnostics.add(frameType);
525         }
526 
removeDiagnostic(int frameType)527         void removeDiagnostic(int frameType) {
528             mActiveDiagnostics.remove(frameType);
529         }
530 
getNumberOfActiveDiagnostic()531         int getNumberOfActiveDiagnostic() {
532             return mActiveDiagnostics.size();
533         }
534 
getDiagnosticArray()535         int[] getDiagnosticArray() {
536             return mActiveDiagnostics.stream().mapToInt(Integer::intValue).toArray();
537         }
538 
getICarDiagnosticEventListener()539         ICarDiagnosticEventListener getICarDiagnosticEventListener() {
540             return mListener;
541         }
542 
543         /** Client dead. should remove all diagnostic requests from client */
544         @Override
binderDied()545         public void binderDied() {
546             mListener.asBinder().unlinkToDeath(this, 0);
547             removeClient(this);
548         }
549 
dispatchDiagnosticUpdate(List<CarDiagnosticEvent> events)550         void dispatchDiagnosticUpdate(List<CarDiagnosticEvent> events) {
551             if (events.size() != 0 && mActive) {
552                 try {
553                     mListener.onDiagnosticEvents(events);
554                 } catch (RemoteException e) {
555                     //ignore. crash will be handled by death handler
556                 }
557             }
558         }
559 
560         @Override
release()561         public void release() {
562             if (mActive) {
563                 mListener.asBinder().unlinkToDeath(this, 0);
564                 mActiveDiagnostics.clear();
565                 mActive = false;
566             }
567         }
568     }
569 
570     private static abstract class DiagnosticRecord {
571         private final ReentrantLock mLock;
572         protected boolean mEnabled = false;
573 
DiagnosticRecord(ReentrantLock lock)574         DiagnosticRecord(ReentrantLock lock) {
575             mLock = lock;
576         }
577 
lock()578         void lock() {
579             mLock.lock();
580         }
581 
unlock()582         void unlock() {
583             mLock.unlock();
584         }
585 
isEnabled()586         boolean isEnabled() {
587             return mEnabled;
588         }
589 
enable()590         void enable() {
591             mEnabled = true;
592         }
593 
disableIfNeeded()594         abstract boolean disableIfNeeded();
update(CarDiagnosticEvent newEvent)595         abstract CarDiagnosticEvent update(CarDiagnosticEvent newEvent);
596     }
597 
598     private static class LiveFrameRecord extends DiagnosticRecord {
599         /** Store the most recent live-frame. */
600         CarDiagnosticEvent mLastEvent = null;
601 
LiveFrameRecord(ReentrantLock lock)602         LiveFrameRecord(ReentrantLock lock) {
603             super(lock);
604         }
605 
606         @Override
disableIfNeeded()607         boolean disableIfNeeded() {
608             if (!mEnabled) return false;
609             mEnabled = false;
610             mLastEvent = null;
611             return true;
612         }
613 
614         @Override
update(@onNull CarDiagnosticEvent newEvent)615         CarDiagnosticEvent update(@NonNull CarDiagnosticEvent newEvent) {
616             Objects.requireNonNull(newEvent);
617             if((null == mLastEvent) || mLastEvent.isEarlierThan(newEvent))
618                 mLastEvent = newEvent;
619             return mLastEvent;
620         }
621 
getLastEvent()622         CarDiagnosticEvent getLastEvent() {
623             return mLastEvent;
624         }
625     }
626 
627     private static class FreezeFrameRecord extends DiagnosticRecord {
628         /** Store the timestamp --> freeze frame mapping. */
629         HashMap<Long, CarDiagnosticEvent> mEvents = new HashMap<>();
630 
FreezeFrameRecord(ReentrantLock lock)631         FreezeFrameRecord(ReentrantLock lock) {
632             super(lock);
633         }
634 
635         @Override
disableIfNeeded()636         boolean disableIfNeeded() {
637             if (!mEnabled) return false;
638             mEnabled = false;
639             clearEvents();
640             return true;
641         }
642 
clearEvents()643         void clearEvents() {
644             mEvents.clear();
645         }
646 
647         @Override
update(@onNull CarDiagnosticEvent newEvent)648         CarDiagnosticEvent update(@NonNull CarDiagnosticEvent newEvent) {
649             mEvents.put(newEvent.timestamp, newEvent);
650             return newEvent;
651         }
652 
getFreezeFrameTimestamps()653         long[] getFreezeFrameTimestamps() {
654             return mEvents.keySet().stream().mapToLong(Long::longValue).toArray();
655         }
656 
getEvent(long timestamp)657         CarDiagnosticEvent getEvent(long timestamp) {
658             return mEvents.get(timestamp);
659         }
660 
getEvents()661         Iterable<CarDiagnosticEvent> getEvents() {
662             return mEvents.values();
663         }
664     }
665 
666     @Override
dump(IndentingPrintWriter writer)667     public void dump(IndentingPrintWriter writer) {
668         writer.println("*CarDiagnosticService*");
669         writer.println("**last events for diagnostics**");
670         if (null != mLiveFrameDiagnosticRecord.getLastEvent()) {
671             writer.println("last live frame event: ");
672             writer.println(mLiveFrameDiagnosticRecord.getLastEvent());
673         }
674         writer.println("freeze frame events: ");
675         mFreezeFrameDiagnosticRecords.getEvents().forEach(writer::println);
676         writer.println("**clients**");
677         try {
678             for (DiagnosticClient client : mClients) {
679                 if (client != null) {
680                     try {
681                         writer.println(
682                                 "binder:"
683                                         + client.mListener
684                                         + " active diagnostics:"
685                                         + Arrays.toString(client.getDiagnosticArray()));
686                     } catch (ConcurrentModificationException e) {
687                         writer.println("concurrent modification happened");
688                     }
689                 } else {
690                     writer.println("null client");
691                 }
692             }
693         } catch (ConcurrentModificationException e) {
694             writer.println("concurrent modification happened");
695         }
696         writer.println("**diagnostic listeners**");
697         try {
698             for (int diagnostic : mDiagnosticListeners.keySet()) {
699                 Listeners diagnosticListeners = mDiagnosticListeners.get(diagnostic);
700                 if (diagnosticListeners != null) {
701                     writer.println(
702                             " Diagnostic:"
703                                     + diagnostic
704                                     + " num client:"
705                                     + diagnosticListeners.getNumberOfClients()
706                                     + " rate:"
707                                     + diagnosticListeners.getRate());
708                 }
709             }
710         } catch (ConcurrentModificationException e) {
711             writer.println("concurrent modification happened");
712         }
713     }
714 }
715