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