/* * 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 android.car.diagnostic; import static com.android.car.internal.common.CommonConstants.EMPTY_LONG_ARRAY; import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.car.Car; import android.car.CarLibLog; import android.car.CarManagerBase; import android.car.diagnostic.ICarDiagnosticEventListener.Stub; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; import android.util.SparseArray; import com.android.car.internal.CarPermission; import com.android.car.internal.CarRatedListeners; import com.android.car.internal.SingleMessageHandler; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; /** * API for monitoring car diagnostic data. * * @hide */ @SystemApi public final class CarDiagnosticManager extends CarManagerBase { public static final int FRAME_TYPE_LIVE = 0; public static final int FRAME_TYPE_FREEZE = 1; @Retention(RetentionPolicy.SOURCE) @IntDef({FRAME_TYPE_LIVE, FRAME_TYPE_FREEZE}) public @interface FrameType {} /** @hide */ public static final @FrameType int[] FRAME_TYPES = { FRAME_TYPE_LIVE, FRAME_TYPE_FREEZE }; private static final int MSG_DIAGNOSTIC_EVENTS = 0; private final ICarDiagnostic mService; private final SparseArray mActiveListeners = new SparseArray<>(); /** Handles call back into clients. */ private final SingleMessageHandler mHandlerCallback; private final CarDiagnosticEventListenerToService mListenerToService; private final CarPermission mVendorExtensionPermission; /** @hide */ public CarDiagnosticManager(Car car, IBinder service) { super(car); mService = ICarDiagnostic.Stub.asInterface(service); mHandlerCallback = new SingleMessageHandler( getEventHandler().getLooper(), MSG_DIAGNOSTIC_EVENTS) { @Override protected void handleEvent(CarDiagnosticEvent event) { CarDiagnosticListeners listeners; synchronized (mActiveListeners) { listeners = mActiveListeners.get(event.frameType); } if (listeners != null) { listeners.onDiagnosticEvent(event); } } }; mVendorExtensionPermission = new CarPermission(getContext(), Car.PERMISSION_VENDOR_EXTENSION); mListenerToService = new CarDiagnosticEventListenerToService(this); } @Override public void onCarDisconnected() { synchronized (mActiveListeners) { mActiveListeners.clear(); } } /** Listener for diagnostic events. Callbacks are called in the Looper context. */ public interface OnDiagnosticEventListener { /** * Called when there is a diagnostic event from the car. * * @param carDiagnosticEvent */ void onDiagnosticEvent(CarDiagnosticEvent carDiagnosticEvent); } // OnDiagnosticEventListener registration private void assertFrameType(@FrameType int frameType) { switch(frameType) { case FRAME_TYPE_FREEZE: case FRAME_TYPE_LIVE: return; default: throw new IllegalArgumentException(String.format( "%d is not a valid diagnostic frame type", frameType)); } } /** * Register a new listener for events of a given frame type and rate. * @param listener * @param frameType * @param rate * @return true if the registration was successful; false otherwise * @throws IllegalArgumentException */ @RequiresPermission(Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL) public boolean registerListener( OnDiagnosticEventListener listener, @FrameType int frameType, int rate) { assertFrameType(frameType); synchronized (mActiveListeners) { boolean needsServerUpdate = false; CarDiagnosticListeners listeners = mActiveListeners.get(frameType); if (listeners == null) { listeners = new CarDiagnosticListeners(rate); mActiveListeners.put(frameType, listeners); needsServerUpdate = true; } if (listeners.addAndUpdateRate(listener, rate)) { needsServerUpdate = true; } if (needsServerUpdate) { if (!registerOrUpdateDiagnosticListener(frameType, rate)) { return false; } } } return true; } /** * Unregister a listener, causing it to stop receiving all diagnostic events. * @param listener */ @RequiresPermission(anyOf = { Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL, Car.PERMISSION_CAR_DIAGNOSTIC_CLEAR}) public void unregisterListener(OnDiagnosticEventListener listener) { synchronized (mActiveListeners) { for (@FrameType int frameType : FRAME_TYPES) { doUnregisterListenerLocked(listener, frameType); } } } private void doUnregisterListenerLocked(OnDiagnosticEventListener listener, @FrameType int frameType) { CarDiagnosticListeners listeners = mActiveListeners.get(frameType); if (listeners != null) { boolean needsServerUpdate = false; if (listeners.contains(listener)) { needsServerUpdate = listeners.remove(listener); } if (listeners.isEmpty()) { try { mService.unregisterDiagnosticListener(frameType, mListenerToService); } catch (RemoteException e) { handleRemoteExceptionFromCarService(e); // continue for local clean-up } mActiveListeners.remove(frameType); } else if (needsServerUpdate) { registerOrUpdateDiagnosticListener(frameType, listeners.getRate()); } } } private boolean registerOrUpdateDiagnosticListener(@FrameType int frameType, int rate) { try { return mService.registerOrUpdateDiagnosticListener(frameType, rate, mListenerToService); } catch (RemoteException e) { return handleRemoteExceptionFromCarService(e, false); } } // ICarDiagnostic forwards /** * Retrieve the most-recently acquired live frame data from the car. * @return A CarDiagnostic event for the most recently known live frame if one is present. * null if no live frame has been recorded by the vehicle. */ @RequiresPermission(anyOf = { Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL, Car.PERMISSION_CAR_DIAGNOSTIC_CLEAR}) public @Nullable CarDiagnosticEvent getLatestLiveFrame() { try { return mService.getLatestLiveFrame(); } catch (RemoteException e) { return handleRemoteExceptionFromCarService(e, null); } } /** * Return the list of the timestamps for which a freeze frame is currently stored. * @return An array containing timestamps at which, at the current time, the vehicle has * a freeze frame stored. If no freeze frames are currently stored, an empty * array will be returned. * Because vehicles might have a limited amount of storage for frames, clients cannot * assume that a timestamp obtained via this call will be indefinitely valid for retrieval * of the actual diagnostic data, and must be prepared to handle a missing frame. */ @RequiresPermission(anyOf = { Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL, Car.PERMISSION_CAR_DIAGNOSTIC_CLEAR}) public long[] getFreezeFrameTimestamps() { try { return mService.getFreezeFrameTimestamps(); } catch (RemoteException e) { return handleRemoteExceptionFromCarService(e, EMPTY_LONG_ARRAY); } } /** * Retrieve the freeze frame event data for a given timestamp, if available. * @param timestamp * @return A CarDiagnostic event for the frame at the given timestamp, if one is * available. null is returned otherwise. * Storage constraints might cause frames to be deleted from vehicle memory. * For this reason it cannot be assumed that a timestamp will yield a valid frame, * even if it was initially obtained via a call to getFreezeFrameTimestamps(). */ @RequiresPermission(anyOf = { Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL, Car.PERMISSION_CAR_DIAGNOSTIC_CLEAR}) public @Nullable CarDiagnosticEvent getFreezeFrame(long timestamp) { try { return mService.getFreezeFrame(timestamp); } catch (RemoteException e) { return handleRemoteExceptionFromCarService(e, null); } } /** * Clear the freeze frame information from vehicle memory at the given timestamps. * @param timestamps A list of timestamps to delete freeze frames at, or an empty array * to delete all freeze frames from vehicle memory. * @return true if all the required frames were deleted (including if no timestamps are * provided and all frames were cleared); false otherwise. * Due to storage constraints, timestamps cannot be assumed to be indefinitely valid, and * a false return from this method should be used by the client as cause for invalidating * its local knowledge of the vehicle diagnostic state. */ @RequiresPermission(Car.PERMISSION_CAR_DIAGNOSTIC_CLEAR) public boolean clearFreezeFrames(long... timestamps) { try { return mService.clearFreezeFrames(timestamps); } catch (RemoteException e) { return handleRemoteExceptionFromCarService(e, false); } } /** * Returns true if this vehicle supports sending live frame information. * @return */ @RequiresPermission(anyOf = { Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL, Car.PERMISSION_CAR_DIAGNOSTIC_CLEAR}) public boolean isLiveFrameSupported() { try { return mService.isLiveFrameSupported(); } catch (RemoteException e) { return handleRemoteExceptionFromCarService(e, false); } } /** * Returns true if this vehicle supports supports sending notifications to * registered listeners when new freeze frames happen. */ @RequiresPermission(anyOf = { Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL, Car.PERMISSION_CAR_DIAGNOSTIC_CLEAR}) public boolean isFreezeFrameNotificationSupported() { try { return mService.isFreezeFrameNotificationSupported(); } catch (RemoteException e) { return handleRemoteExceptionFromCarService(e, false); } } /** * Returns whether the underlying HAL supports retrieving freeze frames * stored in vehicle memory using timestamp. */ @RequiresPermission(anyOf = { Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL, Car.PERMISSION_CAR_DIAGNOSTIC_CLEAR}) public boolean isGetFreezeFrameSupported() { try { return mService.isGetFreezeFrameSupported(); } catch (RemoteException e) { return handleRemoteExceptionFromCarService(e, false); } } /** * Returns true if this vehicle supports clearing all freeze frames. * This is only meaningful if freeze frame data is also supported. * * A return value of true for this method indicates that it is supported to call * carDiagnosticManager.clearFreezeFrames() * to delete all freeze frames stored in vehicle memory. * * @return */ @RequiresPermission(anyOf = { Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL, Car.PERMISSION_CAR_DIAGNOSTIC_CLEAR}) public boolean isClearFreezeFramesSupported() { try { return mService.isClearFreezeFramesSupported(); } catch (RemoteException e) { return handleRemoteExceptionFromCarService(e, false); } } /** * Returns true if this vehicle supports clearing specific freeze frames by timestamp. * This is only meaningful if freeze frame data is also supported. * * A return value of true for this method indicates that it is supported to call * carDiagnosticManager.clearFreezeFrames(timestamp1, timestamp2, ...) * to delete the freeze frames stored for the provided input timestamps, provided any exist. * * @return */ @RequiresPermission(anyOf = { Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL, Car.PERMISSION_CAR_DIAGNOSTIC_CLEAR}) public boolean isSelectiveClearFreezeFramesSupported() { try { return mService.isSelectiveClearFreezeFramesSupported(); } catch (RemoteException e) { return handleRemoteExceptionFromCarService(e, false); } } private static class CarDiagnosticEventListenerToService extends Stub { private final WeakReference mManager; CarDiagnosticEventListenerToService(CarDiagnosticManager manager) { mManager = new WeakReference<>(manager); } private void handleOnDiagnosticEvents(CarDiagnosticManager manager, List events) { manager.mHandlerCallback.sendEvents(events); } @Override public void onDiagnosticEvents(List events) { CarDiagnosticManager manager = mManager.get(); if (manager != null) { handleOnDiagnosticEvents(manager, events); } } } private class CarDiagnosticListeners extends CarRatedListeners { CarDiagnosticListeners(int rate) { super(rate); } void onDiagnosticEvent(final CarDiagnosticEvent event) { // throw away old data as oneway binder call can change order. long updateTime = event.timestamp; if (updateTime < mLastUpdateTime) { Slog.w(CarLibLog.TAG_DIAGNOSTIC, "dropping old data"); return; } mLastUpdateTime = updateTime; final boolean hasVendorExtensionPermission = mVendorExtensionPermission.checkGranted(); final CarDiagnosticEvent eventToDispatch = hasVendorExtensionPermission ? event : event.withVendorSensorsRemoved(); List listeners; synchronized (mActiveListeners) { listeners = new ArrayList<>(getListeners()); } listeners.forEach(new Consumer() { @Override public void accept(OnDiagnosticEventListener listener) { listener.onDiagnosticEvent(eventToDispatch); } }); } } }