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