blob: 88980bf1cbfc713022391ec44d09f7d6c7bfddbb [file] [log] [blame]
/*
* 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 android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.car.Car;
import android.car.CarApiUtil;
import android.car.CarLibLog;
import android.car.CarManagerBase;
import android.car.CarNotConnectedException;
import android.car.diagnostic.ICarDiagnosticEventListener.Stub;
import android.content.Context;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
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 implements 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})
/** @hide */
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<CarDiagnosticListeners> mActiveListeners = new SparseArray<>();
/** Handles call back into clients. */
private final SingleMessageHandler<CarDiagnosticEvent> mHandlerCallback;
private CarDiagnosticEventListenerToService mListenerToService;
private final CarPermission mVendorExtensionPermission;
/** @hide */
public CarDiagnosticManager(IBinder service, Context context, Handler handler) {
mService = ICarDiagnostic.Stub.asInterface(service);
mHandlerCallback = new SingleMessageHandler<CarDiagnosticEvent>(handler.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(context, Car.PERMISSION_VENDOR_EXTENSION);
}
@Override
/** @hide */
public void onCarDisconnected() {
synchronized(mActiveListeners) {
mActiveListeners.clear();
mListenerToService = null;
}
}
/** 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(final 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 CarNotConnectedException
* @throws IllegalArgumentException
*/
public boolean registerListener(OnDiagnosticEventListener listener,
@FrameType int frameType,
int rate)
throws CarNotConnectedException, IllegalArgumentException {
assertFrameType(frameType);
synchronized(mActiveListeners) {
if (null == mListenerToService) {
mListenerToService = new CarDiagnosticEventListenerToService(this);
}
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
*/
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) {
//ignore
}
mActiveListeners.remove(frameType);
} else if (needsServerUpdate) {
try {
registerOrUpdateDiagnosticListener(frameType, listeners.getRate());
} catch (CarNotConnectedException e) {
// ignore
}
}
}
}
private boolean registerOrUpdateDiagnosticListener(@FrameType int frameType, int rate)
throws CarNotConnectedException {
try {
return mService.registerOrUpdateDiagnosticListener(frameType, rate, mListenerToService);
} catch (IllegalStateException e) {
CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
} catch (RemoteException e) {
throw new CarNotConnectedException();
}
return 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.
* @throws CarNotConnectedException
*/
public @Nullable
CarDiagnosticEvent getLatestLiveFrame() throws CarNotConnectedException {
try {
return mService.getLatestLiveFrame();
} catch (IllegalStateException e) {
CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
} catch (RemoteException e) {
throw new CarNotConnectedException();
}
return 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.
* @throws CarNotConnectedException
*/
public long[] getFreezeFrameTimestamps() throws CarNotConnectedException {
try {
return mService.getFreezeFrameTimestamps();
} catch (IllegalStateException e) {
CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
} catch (RemoteException e) {
throw new CarNotConnectedException();
}
return new long[]{};
}
/**
* 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().
* @throws CarNotConnectedException
*/
public @Nullable
CarDiagnosticEvent getFreezeFrame(long timestamp)
throws CarNotConnectedException {
try {
return mService.getFreezeFrame(timestamp);
} catch (IllegalStateException e) {
CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
} catch (RemoteException e) {
throw new CarNotConnectedException();
}
return 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.
* @throws CarNotConnectedException
*/
public boolean clearFreezeFrames(long... timestamps) throws CarNotConnectedException {
try {
return mService.clearFreezeFrames(timestamps);
} catch (IllegalStateException e) {
CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
} catch (RemoteException e) {
throw new CarNotConnectedException();
}
return false;
}
/**
* Returns true if this vehicle supports sending live frame information.
* @return
* @throws CarNotConnectedException
*/
public boolean isLiveFrameSupported() throws CarNotConnectedException {
try {
return mService.isLiveFrameSupported();
} catch (IllegalStateException e) {
CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
} catch (RemoteException e) {
throw new CarNotConnectedException();
}
return false;
}
/**
* Returns true if this vehicle supports supports sending notifications to
* registered listeners when new freeze frames happen.
* @throws CarNotConnectedException
*/
public boolean isFreezeFrameNotificationSupported() throws CarNotConnectedException {
try {
return mService.isFreezeFrameNotificationSupported();
} catch (IllegalStateException e) {
CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
} catch (RemoteException e) {
throw new CarNotConnectedException();
}
return false;
}
/**
* Returns whether the underlying HAL supports retrieving freeze frames
* stored in vehicle memory using timestamp.
* @throws CarNotConnectedException
*/
public boolean isGetFreezeFrameSupported() throws CarNotConnectedException {
try {
return mService.isGetFreezeFrameSupported();
} catch (IllegalStateException e) {
CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
} catch (RemoteException e) {
throw new CarNotConnectedException();
}
return 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
* @throws CarNotConnectedException
*/
public boolean isClearFreezeFramesSupported() throws CarNotConnectedException {
try {
return mService.isClearFreezeFramesSupported();
} catch (IllegalStateException e) {
CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
} catch (RemoteException e) {
throw new CarNotConnectedException();
}
return 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
* @throws CarNotConnectedException
*/
public boolean isSelectiveClearFreezeFramesSupported() throws CarNotConnectedException {
try {
return mService.isSelectiveClearFreezeFramesSupported();
} catch (IllegalStateException e) {
CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
} catch (RemoteException e) {
throw new CarNotConnectedException();
}
return false;
}
private static class CarDiagnosticEventListenerToService
extends Stub {
private final WeakReference<CarDiagnosticManager> mManager;
public CarDiagnosticEventListenerToService(CarDiagnosticManager manager) {
mManager = new WeakReference<>(manager);
}
private void handleOnDiagnosticEvents(CarDiagnosticManager manager,
List<CarDiagnosticEvent> events) {
manager.mHandlerCallback.sendEvents(events);
}
@Override
public void onDiagnosticEvents(List<CarDiagnosticEvent> events) {
CarDiagnosticManager manager = mManager.get();
if (manager != null) {
handleOnDiagnosticEvents(manager, events);
}
}
}
private class CarDiagnosticListeners extends CarRatedListeners<OnDiagnosticEventListener> {
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) {
Log.w(CarLibLog.TAG_DIAGNOSTIC, "dropping old data");
return;
}
mLastUpdateTime = updateTime;
final boolean hasVendorExtensionPermission = mVendorExtensionPermission.checkGranted();
final CarDiagnosticEvent eventToDispatch = hasVendorExtensionPermission ?
event :
event.withVendorSensorsRemoved();
List<OnDiagnosticEventListener> listeners;
synchronized (mActiveListeners) {
listeners = new ArrayList<>(getListeners());
}
listeners.forEach(new Consumer<OnDiagnosticEventListener>() {
@Override
public void accept(OnDiagnosticEventListener listener) {
listener.onDiagnosticEvent(eventToDispatch);
}
});
}
}
}