/* * 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.NonNull; import android.annotation.Nullable; import android.car.Car; import android.car.diagnostic.CarDiagnosticEvent; import android.car.diagnostic.CarDiagnosticManager; import android.car.diagnostic.ICarDiagnostic; import android.car.diagnostic.ICarDiagnosticEventListener; import android.content.Context; import android.os.IBinder; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Log; import com.android.car.hal.DiagnosticHalService.DiagnosticCapabilities; import com.android.car.internal.CarPermission; 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.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; /** @hide */ public class CarDiagnosticService extends ICarDiagnostic.Stub implements CarServiceBase, DiagnosticHalService.DiagnosticListener { /** lock to access diagnostic structures */ private final ReentrantLock mDiagnosticLock = new ReentrantLock(); /** hold clients callback */ @GuardedBy("mDiagnosticLock") private final LinkedList mClients = new LinkedList<>(); /** key: diagnostic type. */ @GuardedBy("mDiagnosticLock") private final HashMap> 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; private final CarPermission mDiagnosticReadPermission; private final CarPermission mDiagnosticClearPermission; public CarDiagnosticService(Context context, DiagnosticHalService diagnosticHal) { mContext = context; mDiagnosticHal = diagnosticHal; mDiagnosticReadPermission = new CarPermission(mContext, Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL); mDiagnosticClearPermission = new CarPermission(mContext, Car.PERMISSION_CAR_DIAGNOSTIC_CLEAR); } @Override public void init() { mDiagnosticLock.lock(); try { mDiagnosticHal.setDiagnosticListener(this); setInitialLiveFrame(); setInitialFreezeFrames(); } finally { mDiagnosticLock.unlock(); } } @Nullable private CarDiagnosticEvent setInitialLiveFrame() { CarDiagnosticEvent liveFrame = null; if(mDiagnosticHal.getDiagnosticCapabilities().isLiveFrameSupported()) { liveFrame = setRecentmostLiveFrame(mDiagnosticHal.getCurrentLiveFrame()); } return liveFrame; } private void setInitialFreezeFrames() { if(mDiagnosticHal.getDiagnosticCapabilities().isFreezeFrameSupported() && mDiagnosticHal.getDiagnosticCapabilities().isFreezeFrameInfoSupported()) { long[] timestamps = mDiagnosticHal.getFreezeFrameTimestamps(); if (timestamps != null) { for (long timestamp : timestamps) { setRecentmostFreezeFrame(mDiagnosticHal.getFreezeFrame(timestamp)); } } } } @Nullable private CarDiagnosticEvent setRecentmostLiveFrame(final CarDiagnosticEvent event) { if (event != null) { return mLiveFrameDiagnosticRecord.update(event.checkLiveFrame()); } return null; } @Nullable private CarDiagnosticEvent setRecentmostFreezeFrame(final CarDiagnosticEvent event) { if (event != null) { return mFreezeFrameDiagnosticRecords.update(event.checkFreezeFrame()); } return null; } @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 events) { ArrayMap> eventsByClient = new ArrayMap<>(); Listeners 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_LIVE); } else if (event.isFreezeFrame()) { setRecentmostFreezeFrame(event); listeners = mDiagnosticListeners.get(CarDiagnosticManager.FRAME_TYPE_FREEZE); } else { Log.w( CarLog.TAG_DIAGNOSTIC, String.format("received unknown diagnostic event: %s", event)); continue; } if (null != listeners) { for (ClientWithRate clientWithRate : listeners.getClients()) { DiagnosticClient client = clientWithRate.getClient(); List clientEvents = eventsByClient.computeIfAbsent(client, (DiagnosticClient diagnosticClient) -> new LinkedList<>()); clientEvents.add(event); } } } mDiagnosticLock.unlock(); for (ArrayMap.Entry> entry : eventsByClient.entrySet()) { CarDiagnosticService.DiagnosticClient client = entry.getKey(); List clientEvents = entry.getValue(); client.dispatchDiagnosticUpdate(clientEvents); } } /** Received diagnostic data from car. */ @Override public void onDiagnosticEvents(List events) { processDiagnosticData(events); } @Override public boolean registerOrUpdateDiagnosticListener(int frameType, int rate, ICarDiagnosticEventListener listener) { boolean shouldStartDiagnostics = false; CarDiagnosticService.DiagnosticClient diagnosticClient = null; Integer oldRate = null; Listeners diagnosticListeners = null; mDiagnosticLock.lock(); try { mDiagnosticReadPermission.assertGranted(); diagnosticClient = findDiagnosticClientLocked(listener); Listeners.ClientWithRate 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); } 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; } 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_LIVE: if (mLiveFrameDiagnosticRecord.isEnabled()) { return true; } if (diagnosticHal.requestSensorStart(CarDiagnosticManager.FRAME_TYPE_LIVE, rate)) { mLiveFrameDiagnosticRecord.enable(); return true; } break; case CarDiagnosticManager.FRAME_TYPE_FREEZE: if (mFreezeFrameDiagnosticRecords.isEnabled()) { return true; } if (diagnosticHal.requestSensorStart(CarDiagnosticManager.FRAME_TYPE_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 diagnosticListeners = mDiagnosticListeners.get(frameType); if (diagnosticListeners == null) { // diagnostic not active return; } ClientWithRate 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_LIVE: if (mLiveFrameDiagnosticRecord.disableIfNeeded()) diagnosticHal.requestSensorStop(CarDiagnosticManager.FRAME_TYPE_LIVE); break; case CarDiagnosticManager.FRAME_TYPE_FREEZE: if (mFreezeFrameDiagnosticRecords.disableIfNeeded()) diagnosticHal.requestSensorStop(CarDiagnosticManager.FRAME_TYPE_FREEZE); break; } } private DiagnosticHalService getDiagnosticHal() { return mDiagnosticHal; } // Expose DiagnosticCapabilities public boolean isLiveFrameSupported() { return getDiagnosticHal().getDiagnosticCapabilities().isLiveFrameSupported(); } public boolean isFreezeFrameNotificationSupported() { return getDiagnosticHal().getDiagnosticCapabilities().isFreezeFrameSupported(); } public boolean isGetFreezeFrameSupported() { DiagnosticCapabilities diagnosticCapabilities = getDiagnosticHal().getDiagnosticCapabilities(); return diagnosticCapabilities.isFreezeFrameInfoSupported() && diagnosticCapabilities.isFreezeFrameSupported(); } public boolean isClearFreezeFramesSupported() { DiagnosticCapabilities diagnosticCapabilities = getDiagnosticHal().getDiagnosticCapabilities(); return diagnosticCapabilities.isFreezeFrameClearSupported() && diagnosticCapabilities.isFreezeFrameSupported(); } public boolean isSelectiveClearFreezeFramesSupported() { DiagnosticCapabilities diagnosticCapabilities = getDiagnosticHal().getDiagnosticCapabilities(); return isClearFreezeFramesSupported() && diagnosticCapabilities.isSelectiveClearFreezeFramesSupported(); } // 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) { mDiagnosticClearPermission.assertGranted(); if (!isClearFreezeFramesSupported()) return false; if (timestamps != null && timestamps.length != 0) { if (!isSelectiveClearFreezeFramesSupported()) { return false; } } 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. */ @GuardedBy("mDiagnosticLock") 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(); } } /** internal instance for pending client request */ private class DiagnosticClient implements Listeners.IListener { /** callback for diagnostic events */ private final ICarDiagnosticEventListener mListener; private final Set 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) { return o instanceof DiagnosticClient && mListener.asBinder() == ((DiagnosticClient) o).mListener.asBinder(); } 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 events) { if (events.size() != 0 && mActive) { try { mListener.onDiagnosticEvents(events); } catch (RemoteException e) { //ignore. crash will be handled by death handler } } } @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(@NonNull CarDiagnosticEvent 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 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(@NonNull 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 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"); } } }