Post-O. Sketch out CarDiagnosticManager API surface
Provide an implementation of all layers required to plumb CarDiagnosticManager through:
* CarDiagnosticEvent
* DiagnosticHalService
* CarDiagnosticService
* CarDiagnosticManager
If FutureFeatures are enabled, this is integrated end-to-end enough to run trivial tests of the API
Test: build with TARGET_USES_CAR_FUTURE_FEATURES=true then at a shell runtest -x packages/services/Car/tests/android_car_api_test/ -c android.car.apitest.CarDiagnosticManagerTest
Change-Id: I0f2aafd039d26fec15182dd7029cf8c7995ce85b
diff --git a/service/src/com/android/car/CarDiagnosticService.java b/service/src/com/android/car/CarDiagnosticService.java
new file mode 100644
index 0000000..3a02010
--- /dev/null
+++ b/service/src/com/android/car/CarDiagnosticService.java
@@ -0,0 +1,793 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car;
+
+import android.annotation.Nullable;
+import android.car.annotation.FutureFeature;
+import android.car.hardware.CarDiagnosticEvent;
+import android.car.hardware.CarDiagnosticManager;
+import android.car.hardware.ICarDiagnostic;
+import android.car.hardware.ICarDiagnosticEventListener;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.Log;
+import com.android.car.Listeners.ClientWithRate;
+import com.android.car.hal.DiagnosticHalService;
+import com.android.internal.annotations.GuardedBy;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.ConcurrentModificationException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
+
+@FutureFeature
+/** @hide */
+public class CarDiagnosticService extends ICarDiagnostic.Stub
+ implements CarServiceBase, DiagnosticHalService.DiagnosticListener {
+ /** {@link #mDiagnosticLock} is not waited forever for handling disconnection */
+ private static final long MAX_DIAGNOSTIC_LOCK_WAIT_MS = 1000;
+
+ /** lock to access diagnostic structures */
+ private final ReentrantLock mDiagnosticLock = new ReentrantLock();
+ /** hold clients callback */
+ @GuardedBy("mDiagnosticLock")
+ private final LinkedList<DiagnosticClient> mClients = new LinkedList<>();
+
+ /** key: diagnostic type. */
+ @GuardedBy("mDiagnosticLock")
+ private final HashMap<Integer, Listeners<DiagnosticClient>> mDiagnosticListeners =
+ new HashMap<>();
+
+ /** the latest live frame data. */
+ @GuardedBy("mDiagnosticLock")
+ private final LiveFrameRecord mLiveFrameDiagnosticRecord = new LiveFrameRecord(mDiagnosticLock);
+
+ /** the latest freeze frame data (key: DTC) */
+ @GuardedBy("mDiagnosticLock")
+ private final FreezeFrameRecord mFreezeFrameDiagnosticRecords = new FreezeFrameRecord(
+ mDiagnosticLock);
+
+ private final DiagnosticHalService mDiagnosticHal;
+
+ private final Context mContext;
+
+ public CarDiagnosticService(Context context, DiagnosticHalService diagnosticHal) {
+ mContext = context;
+ mDiagnosticHal = diagnosticHal;
+ }
+
+ @Override
+ public void init() {
+ mDiagnosticLock.lock();
+ try {
+ mDiagnosticHal.setDiagnosticListener(this);
+ setInitialLiveFrame();
+ setInitialFreezeFrames();
+ } finally {
+ mDiagnosticLock.unlock();
+ }
+ }
+
+ private CarDiagnosticEvent setInitialLiveFrame() {
+ CarDiagnosticEvent liveFrame = setRecentmostLiveFrame(mDiagnosticHal.getCurrentLiveFrame());
+ return liveFrame;
+ }
+
+ private void setInitialFreezeFrames() {
+ for (long timestamp: mDiagnosticHal.getFreezeFrameTimestamps()) {
+ setRecentmostFreezeFrame(mDiagnosticHal.getFreezeFrame(timestamp));
+ }
+ }
+
+ private CarDiagnosticEvent setRecentmostLiveFrame(final CarDiagnosticEvent event) {
+ return mLiveFrameDiagnosticRecord.update(Objects.requireNonNull(event).checkLiveFrame());
+ }
+
+ private CarDiagnosticEvent setRecentmostFreezeFrame(final CarDiagnosticEvent event) {
+ return mFreezeFrameDiagnosticRecords.update(
+ Objects.requireNonNull(event).checkFreezeFrame());
+ }
+
+ @Override
+ public void release() {
+ mDiagnosticLock.lock();
+ try {
+ mDiagnosticListeners.forEach(
+ (Integer frameType, Listeners diagnosticListeners) ->
+ diagnosticListeners.release());
+ mDiagnosticListeners.clear();
+ mLiveFrameDiagnosticRecord.disableIfNeeded();
+ mFreezeFrameDiagnosticRecords.disableIfNeeded();
+ mClients.clear();
+ } finally {
+ mDiagnosticLock.unlock();
+ }
+ }
+
+ private void processDiagnosticData(List<CarDiagnosticEvent> events) {
+ ArrayMap<CarDiagnosticService.DiagnosticClient, List<CarDiagnosticEvent>> eventsByClient =
+ new ArrayMap<>();
+
+ Listeners<DiagnosticClient> listeners = null;
+
+ mDiagnosticLock.lock();
+ for (CarDiagnosticEvent event : events) {
+ if (event.isLiveFrame()) {
+ // record recent-most live frame information
+ setRecentmostLiveFrame(event);
+ listeners = mDiagnosticListeners.get(CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE);
+ } else if (event.isFreezeFrame()) {
+ setRecentmostFreezeFrame(event);
+ listeners = mDiagnosticListeners.get(CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE);
+ } else {
+ Log.w(
+ CarLog.TAG_DIAGNOSTIC,
+ String.format("received unknown diagnostic event: %s", event));
+ continue;
+ }
+
+ if (null != listeners) {
+ for (ClientWithRate<DiagnosticClient> clientWithRate : listeners.getClients()) {
+ DiagnosticClient client = clientWithRate.getClient();
+ List<CarDiagnosticEvent> clientEvents = eventsByClient.computeIfAbsent(client,
+ (DiagnosticClient diagnosticClient) -> new LinkedList<>());
+ clientEvents.add(event);
+ }
+ }
+ }
+ mDiagnosticLock.unlock();
+
+ for (ArrayMap.Entry<CarDiagnosticService.DiagnosticClient, List<CarDiagnosticEvent>> entry :
+ eventsByClient.entrySet()) {
+ CarDiagnosticService.DiagnosticClient client = entry.getKey();
+ List<CarDiagnosticEvent> clientEvents = entry.getValue();
+
+ client.dispatchDiagnosticUpdate(clientEvents);
+ }
+ }
+
+ /** Received diagnostic data from car. */
+ @Override
+ public void onDiagnosticEvents(List<CarDiagnosticEvent> events) {
+ processDiagnosticData(events);
+ }
+
+ private List<CarDiagnosticEvent> getCachedEventsLocked(int frameType) {
+ ArrayList<CarDiagnosticEvent> events = new ArrayList<>();
+ switch (frameType) {
+ case CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE:
+ mLiveFrameDiagnosticRecord.lock();
+ events.add(mLiveFrameDiagnosticRecord.getLastEvent());
+ mLiveFrameDiagnosticRecord.unlock();
+ break;
+ case CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE:
+ mFreezeFrameDiagnosticRecords.lock();
+ mFreezeFrameDiagnosticRecords.getEvents().forEach(events::add);
+ mFreezeFrameDiagnosticRecords.unlock();
+ break;
+ default: break;
+ }
+ return events;
+ }
+
+ private void assertPermission(int frameType) {
+ if (Binder.getCallingUid() != Process.myUid()) {
+ switch (getDiagnosticPermission(frameType)) {
+ case PackageManager.PERMISSION_GRANTED:
+ break;
+ default:
+ throw new SecurityException(
+ "client does not have permission:"
+ + getPermissionName(frameType)
+ + " pid:"
+ + Binder.getCallingPid()
+ + " uid:"
+ + Binder.getCallingUid());
+ }
+ }
+ }
+
+ @Override
+ public boolean registerOrUpdateDiagnosticListener(int frameType, int rate,
+ ICarDiagnosticEventListener listener) {
+ boolean shouldStartDiagnostics = false;
+ CarDiagnosticService.DiagnosticClient diagnosticClient = null;
+ Integer oldRate = null;
+ Listeners<DiagnosticClient> diagnosticListeners = null;
+ mDiagnosticLock.lock();
+ try {
+ assertPermission(frameType);
+ diagnosticClient = findDiagnosticClientLocked(listener);
+ Listeners.ClientWithRate<DiagnosticClient> diagnosticClientWithRate = null;
+ if (diagnosticClient == null) {
+ diagnosticClient = new DiagnosticClient(listener);
+ try {
+ listener.asBinder().linkToDeath(diagnosticClient, 0);
+ } catch (RemoteException e) {
+ Log.w(
+ CarLog.TAG_DIAGNOSTIC,
+ String.format(
+ "received RemoteException trying to register listener for %s",
+ frameType));
+ return false;
+ }
+ mClients.add(diagnosticClient);
+ }
+ // If we have a cached event for this diagnostic, send the event.
+ diagnosticClient.dispatchDiagnosticUpdate(getCachedEventsLocked(frameType));
+ diagnosticListeners = mDiagnosticListeners.get(frameType);
+ if (diagnosticListeners == null) {
+ diagnosticListeners = new Listeners<>(rate);
+ mDiagnosticListeners.put(frameType, diagnosticListeners);
+ shouldStartDiagnostics = true;
+ } else {
+ oldRate = diagnosticListeners.getRate();
+ diagnosticClientWithRate =
+ diagnosticListeners.findClientWithRate(diagnosticClient);
+ }
+ if (diagnosticClientWithRate == null) {
+ diagnosticClientWithRate =
+ new ClientWithRate<>(diagnosticClient, rate);
+ diagnosticListeners.addClientWithRate(diagnosticClientWithRate);
+ } else {
+ diagnosticClientWithRate.setRate(rate);
+ }
+ if (diagnosticListeners.getRate() > rate) {
+ diagnosticListeners.setRate(rate);
+ shouldStartDiagnostics = true;
+ }
+ diagnosticClient.addDiagnostic(frameType);
+ } finally {
+ mDiagnosticLock.unlock();
+ }
+ Log.i(
+ CarLog.TAG_DIAGNOSTIC,
+ String.format(
+ "shouldStartDiagnostics = %s for %s at rate %d",
+ shouldStartDiagnostics, frameType, rate));
+ // start diagnostic outside lock as it can take time.
+ if (shouldStartDiagnostics) {
+ if (!startDiagnostic(frameType, rate)) {
+ // failed. so remove from active diagnostic list.
+ Log.w(CarLog.TAG_DIAGNOSTIC, "startDiagnostic failed");
+ mDiagnosticLock.lock();
+ try {
+ diagnosticClient.removeDiagnostic(frameType);
+ if (oldRate != null) {
+ diagnosticListeners.setRate(oldRate);
+ } else {
+ mDiagnosticListeners.remove(frameType);
+ }
+ } finally {
+ mDiagnosticLock.unlock();
+ }
+ return false;
+ }
+ }
+ return true;
+ }
+
+ //TODO(egranata): handle permissions correctly
+ private int getDiagnosticPermission(int frameType) {
+ String permission = getPermissionName(frameType);
+ int result = PackageManager.PERMISSION_GRANTED;
+ if (permission != null) {
+ return mContext.checkCallingOrSelfPermission(permission);
+ }
+ // If no permission is required, return granted.
+ return result;
+ }
+
+ private String getPermissionName(int frameType) {
+ return null;
+ }
+
+ private boolean startDiagnostic(int frameType, int rate) {
+ Log.i(CarLog.TAG_DIAGNOSTIC, String.format("starting diagnostic %s at rate %d",
+ frameType, rate));
+ DiagnosticHalService diagnosticHal = getDiagnosticHal();
+ if (diagnosticHal != null) {
+ if (!diagnosticHal.isReady()) {
+ Log.w(CarLog.TAG_DIAGNOSTIC, "diagnosticHal not ready");
+ return false;
+ }
+ switch (frameType) {
+ case CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE:
+ if (mLiveFrameDiagnosticRecord.isEnabled()) {
+ return true;
+ }
+ if (diagnosticHal.requestSensorStart(CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE,
+ rate)) {
+ mLiveFrameDiagnosticRecord.enable();
+ return true;
+ }
+ break;
+ case CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE:
+ if (mFreezeFrameDiagnosticRecords.isEnabled()) {
+ return true;
+ }
+ if (diagnosticHal.requestSensorStart(CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE,
+ rate)) {
+ mFreezeFrameDiagnosticRecords.enable();
+ return true;
+ }
+ break;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void unregisterDiagnosticListener(
+ int frameType, ICarDiagnosticEventListener listener) {
+ boolean shouldStopDiagnostic = false;
+ boolean shouldRestartDiagnostic = false;
+ int newRate = 0;
+ mDiagnosticLock.lock();
+ try {
+ DiagnosticClient diagnosticClient = findDiagnosticClientLocked(listener);
+ if (diagnosticClient == null) {
+ Log.i(
+ CarLog.TAG_DIAGNOSTIC,
+ String.format(
+ "trying to unregister diagnostic client %s for %s which is not registered",
+ listener, frameType));
+ // never registered or already unregistered.
+ return;
+ }
+ diagnosticClient.removeDiagnostic(frameType);
+ if (diagnosticClient.getNumberOfActiveDiagnostic() == 0) {
+ diagnosticClient.release();
+ mClients.remove(diagnosticClient);
+ }
+ Listeners<DiagnosticClient> diagnosticListeners = mDiagnosticListeners.get(frameType);
+ if (diagnosticListeners == null) {
+ // diagnostic not active
+ return;
+ }
+ ClientWithRate<DiagnosticClient> clientWithRate =
+ diagnosticListeners.findClientWithRate(diagnosticClient);
+ if (clientWithRate == null) {
+ return;
+ }
+ diagnosticListeners.removeClientWithRate(clientWithRate);
+ if (diagnosticListeners.getNumberOfClients() == 0) {
+ shouldStopDiagnostic = true;
+ mDiagnosticListeners.remove(frameType);
+ } else if (diagnosticListeners.updateRate()) { // rate changed
+ newRate = diagnosticListeners.getRate();
+ shouldRestartDiagnostic = true;
+ }
+ } finally {
+ mDiagnosticLock.unlock();
+ }
+ Log.i(
+ CarLog.TAG_DIAGNOSTIC,
+ String.format(
+ "shouldStopDiagnostic = %s, shouldRestartDiagnostic = %s for type %s",
+ shouldStopDiagnostic, shouldRestartDiagnostic, frameType));
+ if (shouldStopDiagnostic) {
+ stopDiagnostic(frameType);
+ } else if (shouldRestartDiagnostic) {
+ startDiagnostic(frameType, newRate);
+ }
+ }
+
+ private void stopDiagnostic(int frameType) {
+ DiagnosticHalService diagnosticHal = getDiagnosticHal();
+ if (diagnosticHal == null || !diagnosticHal.isReady()) {
+ Log.w(CarLog.TAG_DIAGNOSTIC, "diagnosticHal not ready");
+ return;
+ }
+ switch (frameType) {
+ case CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE:
+ if (mLiveFrameDiagnosticRecord.disableIfNeeded())
+ diagnosticHal.requestSensorStop(CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE);
+ break;
+ case CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE:
+ if (mFreezeFrameDiagnosticRecords.disableIfNeeded())
+ diagnosticHal.requestSensorStop(CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE);
+ break;
+ }
+ }
+
+ private DiagnosticHalService getDiagnosticHal() {
+ return mDiagnosticHal;
+ }
+
+ // ICarDiagnostic implementations
+
+ @Override
+ public CarDiagnosticEvent getLatestLiveFrame() {
+ mLiveFrameDiagnosticRecord.lock();
+ CarDiagnosticEvent liveFrame = mLiveFrameDiagnosticRecord.getLastEvent();
+ mLiveFrameDiagnosticRecord.unlock();
+ return liveFrame;
+ }
+
+ @Override
+ public long[] getFreezeFrameTimestamps() {
+ mFreezeFrameDiagnosticRecords.lock();
+ long[] timestamps = mFreezeFrameDiagnosticRecords.getFreezeFrameTimestamps();
+ mFreezeFrameDiagnosticRecords.unlock();
+ return timestamps;
+ }
+
+ @Override
+ @Nullable
+ public CarDiagnosticEvent getFreezeFrame(long timestamp) {
+ mFreezeFrameDiagnosticRecords.lock();
+ CarDiagnosticEvent freezeFrame = mFreezeFrameDiagnosticRecords.getEvent(timestamp);
+ mFreezeFrameDiagnosticRecords.unlock();
+ return freezeFrame;
+ }
+
+ @Override
+ public boolean clearFreezeFrames(long... timestamps) {
+ mFreezeFrameDiagnosticRecords.lock();
+ mDiagnosticHal.clearFreezeFrames(timestamps);
+ mFreezeFrameDiagnosticRecords.clearEvents();
+ mFreezeFrameDiagnosticRecords.unlock();
+ return true;
+ }
+
+ /**
+ * Find DiagnosticClient from client list and return it. This should be called with mClients
+ * locked.
+ *
+ * @param listener
+ * @return null if not found.
+ */
+ private CarDiagnosticService.DiagnosticClient findDiagnosticClientLocked(
+ ICarDiagnosticEventListener listener) {
+ IBinder binder = listener.asBinder();
+ for (DiagnosticClient diagnosticClient : mClients) {
+ if (diagnosticClient.isHoldingListenerBinder(binder)) {
+ return diagnosticClient;
+ }
+ }
+ return null;
+ }
+
+ private void removeClient(DiagnosticClient diagnosticClient) {
+ mDiagnosticLock.lock();
+ try {
+ for (int diagnostic : diagnosticClient.getDiagnosticArray()) {
+ unregisterDiagnosticListener(
+ diagnostic, diagnosticClient.getICarDiagnosticEventListener());
+ }
+ mClients.remove(diagnosticClient);
+ } finally {
+ mDiagnosticLock.unlock();
+ }
+ }
+
+ private class DiagnosticDispatchHandler extends Handler {
+ private static final long DIAGNOSTIC_DISPATCH_MIN_INTERVAL_MS = 16; // over 60Hz
+
+ private static final int MSG_DIAGNOSTIC_DATA = 0;
+
+ private long mLastDiagnosticDispatchTime = -1;
+ private int mFreeListIndex = 0;
+ private final LinkedList<CarDiagnosticEvent>[] mDiagnosticDataList = new LinkedList[2];
+
+ private DiagnosticDispatchHandler(Looper looper) {
+ super(looper);
+ for (int i = 0; i < mDiagnosticDataList.length; i++) {
+ mDiagnosticDataList[i] = new LinkedList<CarDiagnosticEvent>();
+ }
+ }
+
+ private synchronized void handleDiagnosticEvents(List<CarDiagnosticEvent> data) {
+ LinkedList<CarDiagnosticEvent> list = mDiagnosticDataList[mFreeListIndex];
+ list.addAll(data);
+ requestDispatchLocked();
+ }
+
+ private synchronized void handleDiagnosticEvent(CarDiagnosticEvent event) {
+ LinkedList<CarDiagnosticEvent> list = mDiagnosticDataList[mFreeListIndex];
+ list.add(event);
+ requestDispatchLocked();
+ }
+
+ private void requestDispatchLocked() {
+ Message msg = obtainMessage(MSG_DIAGNOSTIC_DATA);
+ long now = SystemClock.uptimeMillis();
+ long delta = now - mLastDiagnosticDispatchTime;
+ if (delta > DIAGNOSTIC_DISPATCH_MIN_INTERVAL_MS) {
+ sendMessage(msg);
+ } else {
+ sendMessageDelayed(msg, DIAGNOSTIC_DISPATCH_MIN_INTERVAL_MS - delta);
+ }
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DIAGNOSTIC_DATA:
+ doHandleDiagnosticData();
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void doHandleDiagnosticData() {
+ List<CarDiagnosticEvent> listToDispatch = null;
+ synchronized (this) {
+ mLastDiagnosticDispatchTime = SystemClock.uptimeMillis();
+ int nonFreeListIndex = mFreeListIndex ^ 0x1;
+ List<CarDiagnosticEvent> nonFreeList = mDiagnosticDataList[nonFreeListIndex];
+ List<CarDiagnosticEvent> freeList = mDiagnosticDataList[mFreeListIndex];
+ if (nonFreeList.size() > 0) {
+ // copy again, but this should not be normal case
+ nonFreeList.addAll(freeList);
+ listToDispatch = nonFreeList;
+ freeList.clear();
+ } else if (freeList.size() > 0) {
+ listToDispatch = freeList;
+ mFreeListIndex = nonFreeListIndex;
+ }
+ }
+ // leave this part outside lock so that time-taking dispatching can be done without
+ // blocking diagnostic event notification.
+ if (listToDispatch != null) {
+ processDiagnosticData(listToDispatch);
+ listToDispatch.clear();
+ }
+ }
+ }
+
+ /** internal instance for pending client request */
+ private class DiagnosticClient implements Listeners.IListener {
+ /** callback for diagnostic events */
+ private final ICarDiagnosticEventListener mListener;
+
+ private final Set<Integer> mActiveDiagnostics = new HashSet<>();
+
+ /** when false, it is already released */
+ private volatile boolean mActive = true;
+
+ DiagnosticClient(ICarDiagnosticEventListener listener) {
+ this.mListener = listener;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof CarDiagnosticService.DiagnosticClient
+ && mListener.asBinder()
+ == ((CarDiagnosticService.DiagnosticClient) o).mListener.asBinder()) {
+ return true;
+ }
+ return false;
+ }
+
+ boolean isHoldingListenerBinder(IBinder listenerBinder) {
+ return mListener.asBinder() == listenerBinder;
+ }
+
+ void addDiagnostic(int frameType) {
+ mActiveDiagnostics.add(frameType);
+ }
+
+ void removeDiagnostic(int frameType) {
+ mActiveDiagnostics.remove(frameType);
+ }
+
+ int getNumberOfActiveDiagnostic() {
+ return mActiveDiagnostics.size();
+ }
+
+ int[] getDiagnosticArray() {
+ return mActiveDiagnostics.stream().mapToInt(Integer::intValue).toArray();
+ }
+
+ ICarDiagnosticEventListener getICarDiagnosticEventListener() {
+ return mListener;
+ }
+
+ /** Client dead. should remove all diagnostic requests from client */
+ @Override
+ public void binderDied() {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ removeClient(this);
+ }
+
+ void dispatchDiagnosticUpdate(List<CarDiagnosticEvent> events) {
+ if (events.size() == 0) {
+ return;
+ }
+ if (mActive) {
+ try {
+ mListener.onDiagnosticEvents(events);
+ } catch (RemoteException e) {
+ //ignore. crash will be handled by death handler
+ }
+ } else {
+ }
+ }
+
+ @Override
+ public void release() {
+ if (mActive) {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ mActiveDiagnostics.clear();
+ mActive = false;
+ }
+ }
+ }
+
+ private static abstract class DiagnosticRecord {
+ private final ReentrantLock mLock;
+ protected boolean mEnabled = false;
+
+ DiagnosticRecord(ReentrantLock lock) {
+ mLock = lock;
+ }
+
+ void lock() {
+ mLock.lock();
+ }
+
+ void unlock() {
+ mLock.unlock();
+ }
+
+ boolean isEnabled() {
+ return mEnabled;
+ }
+
+ void enable() {
+ mEnabled = true;
+ }
+
+ abstract boolean disableIfNeeded();
+ abstract CarDiagnosticEvent update(CarDiagnosticEvent newEvent);
+ }
+
+ private static class LiveFrameRecord extends DiagnosticRecord {
+ /** Store the most recent live-frame. */
+ CarDiagnosticEvent mLastEvent = null;
+
+ LiveFrameRecord(ReentrantLock lock) {
+ super(lock);
+ }
+
+ @Override
+ boolean disableIfNeeded() {
+ if (!mEnabled) return false;
+ mEnabled = false;
+ mLastEvent = null;
+ return true;
+ }
+
+ @Override
+ CarDiagnosticEvent update(CarDiagnosticEvent newEvent) {
+ newEvent = Objects.requireNonNull(newEvent);
+ if((null == mLastEvent) || mLastEvent.isEarlierThan(newEvent))
+ mLastEvent = newEvent;
+ return mLastEvent;
+ }
+
+ CarDiagnosticEvent getLastEvent() {
+ return mLastEvent;
+ }
+ }
+
+ private static class FreezeFrameRecord extends DiagnosticRecord {
+ /** Store the timestamp --> freeze frame mapping. */
+ HashMap<Long, CarDiagnosticEvent> mEvents = new HashMap<>();
+
+ FreezeFrameRecord(ReentrantLock lock) {
+ super(lock);
+ }
+
+ @Override
+ boolean disableIfNeeded() {
+ if (!mEnabled) return false;
+ mEnabled = false;
+ clearEvents();
+ return true;
+ }
+
+ void clearEvents() {
+ mEvents.clear();
+ }
+
+ @Override
+ CarDiagnosticEvent update(CarDiagnosticEvent newEvent) {
+ mEvents.put(newEvent.timestamp, newEvent);
+ return newEvent;
+ }
+
+ long[] getFreezeFrameTimestamps() {
+ return mEvents.keySet().stream().mapToLong(Long::longValue).toArray();
+ }
+
+ CarDiagnosticEvent getEvent(long timestamp) {
+ return mEvents.get(timestamp);
+ }
+
+ Iterable<CarDiagnosticEvent> getEvents() {
+ return mEvents.values();
+ }
+ }
+
+ @Override
+ public void dump(PrintWriter writer) {
+ writer.println("*CarDiagnosticService*");
+ writer.println("**last events for diagnostics**");
+ if (null != mLiveFrameDiagnosticRecord.getLastEvent()) {
+ writer.println("last live frame event: ");
+ writer.println(mLiveFrameDiagnosticRecord.getLastEvent());
+ }
+ writer.println("freeze frame events: ");
+ mFreezeFrameDiagnosticRecords.getEvents().forEach(writer::println);
+ writer.println("**clients**");
+ try {
+ for (DiagnosticClient client : mClients) {
+ if (client != null) {
+ try {
+ writer.println(
+ "binder:"
+ + client.mListener
+ + " active diagnostics:"
+ + Arrays.toString(client.getDiagnosticArray()));
+ } catch (ConcurrentModificationException e) {
+ writer.println("concurrent modification happened");
+ }
+ } else {
+ writer.println("null client");
+ }
+ }
+ } catch (ConcurrentModificationException e) {
+ writer.println("concurrent modification happened");
+ }
+ writer.println("**diagnostic listeners**");
+ try {
+ for (int diagnostic : mDiagnosticListeners.keySet()) {
+ Listeners diagnosticListeners = mDiagnosticListeners.get(diagnostic);
+ if (diagnosticListeners != null) {
+ writer.println(
+ " Diagnostic:"
+ + diagnostic
+ + " num client:"
+ + diagnosticListeners.getNumberOfClients()
+ + " rate:"
+ + diagnosticListeners.getRate());
+ }
+ }
+ } catch (ConcurrentModificationException e) {
+ writer.println("concurrent modification happened");
+ }
+ }
+}