blob: 146dd95948c49bb8501f1ea99aacef6a6480168c [file] [log] [blame]
/*
* Copyright (C) 2018 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.Car;
import android.car.VehicleAreaType;
import android.car.drivingstate.CarDrivingStateEvent;
import android.car.drivingstate.CarDrivingStateEvent.CarDrivingState;
import android.car.drivingstate.ICarDrivingState;
import android.car.drivingstate.ICarDrivingStateChangeListener;
import android.car.hardware.CarPropertyConfig;
import android.car.hardware.CarPropertyValue;
import android.car.hardware.property.CarPropertyEvent;
import android.car.hardware.property.ICarPropertyEventListener;
import android.content.Context;
import android.hardware.automotive.vehicle.V2_0.VehicleGear;
import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.LinkedList;
import java.util.List;
/**
* A service that infers the current driving state of the vehicle. It computes the driving state
* from listening to relevant properties from {@link CarPropertyService}
*/
public class CarDrivingStateService extends ICarDrivingState.Stub implements CarServiceBase {
private static final String TAG = CarLog.tagFor(CarDrivingStateService.class);
private static final boolean DBG = false;
private static final int MAX_TRANSITION_LOG_SIZE = 20;
private static final int PROPERTY_UPDATE_RATE = 5; // Update rate in Hz
private static final int NOT_RECEIVED = -1;
private final Context mContext;
private final CarPropertyService mPropertyService;
// List of clients listening to driving state events.
private final RemoteCallbackList<ICarDrivingStateChangeListener> mDrivingStateClients =
new RemoteCallbackList<>();
// Array of properties that the service needs to listen to from CarPropertyService for deriving
// the driving state.
private static final int[] REQUIRED_PROPERTIES = {
VehicleProperty.PERF_VEHICLE_SPEED,
VehicleProperty.GEAR_SELECTION,
VehicleProperty.PARKING_BRAKE_ON};
private final HandlerThread mClientDispatchThread = CarServiceUtils.getHandlerThread(
getClass().getSimpleName());
private final Handler mClientDispatchHandler = new Handler(mClientDispatchThread.getLooper());
private final Object mLock = new Object();
// For dumpsys logging
@GuardedBy("mLock")
private final LinkedList<Utils.TransitionLog> mTransitionLogs = new LinkedList<>();
@GuardedBy("mLock")
private int mLastGear;
@GuardedBy("mLock")
private long mLastGearTimestamp = NOT_RECEIVED;
@GuardedBy("mLock")
private float mLastSpeed;
@GuardedBy("mLock")
private long mLastSpeedTimestamp = NOT_RECEIVED;
@GuardedBy("mLock")
private boolean mLastParkingBrakeState;
@GuardedBy("mLock")
private long mLastParkingBrakeTimestamp = NOT_RECEIVED;
@GuardedBy("mLock")
private List<Integer> mSupportedGears;
@GuardedBy("mLock")
private CarDrivingStateEvent mCurrentDrivingState;
public CarDrivingStateService(Context context, CarPropertyService propertyService) {
mContext = context;
mPropertyService = propertyService;
mCurrentDrivingState = createDrivingStateEvent(CarDrivingStateEvent.DRIVING_STATE_UNKNOWN);
}
@Override
public void init() {
if (!checkPropertySupport()) {
Slog.e(TAG, "init failure. Driving state will always be fully restrictive");
return;
}
// Gets the boot state first, before getting any events from car.
synchronized (mLock) {
mCurrentDrivingState = createDrivingStateEvent(inferDrivingStateLocked());
addTransitionLogLocked(TAG + " Boot", CarDrivingStateEvent.DRIVING_STATE_UNKNOWN,
mCurrentDrivingState.eventValue, mCurrentDrivingState.timeStamp);
}
subscribeToProperties();
}
@Override
public void release() {
for (int property : REQUIRED_PROPERTIES) {
mPropertyService.unregisterListener(property, mICarPropertyEventListener);
}
while (mDrivingStateClients.getRegisteredCallbackCount() > 0) {
for (int i = mDrivingStateClients.getRegisteredCallbackCount() - 1; i >= 0; i--) {
ICarDrivingStateChangeListener client =
mDrivingStateClients.getRegisteredCallbackItem(i);
if (client == null) {
continue;
}
mDrivingStateClients.unregister(client);
}
}
mCurrentDrivingState = createDrivingStateEvent(CarDrivingStateEvent.DRIVING_STATE_UNKNOWN);
}
/**
* Checks if the {@link CarPropertyService} supports the required properties.
*
* @return {@code true} if supported, {@code false} if not
*/
private boolean checkPropertySupport() {
List<CarPropertyConfig> configs = mPropertyService
.getPropertyConfigList(REQUIRED_PROPERTIES);
for (int propertyId : REQUIRED_PROPERTIES) {
boolean found = false;
for (CarPropertyConfig config : configs) {
if (config.getPropertyId() == propertyId) {
found = true;
break;
}
}
if (!found) {
Slog.e(TAG, "Required property not supported: " + propertyId);
return false;
}
}
return true;
}
/**
* Subscribe to the {@link CarPropertyService} for required sensors.
*/
private void subscribeToProperties() {
for (int propertyId : REQUIRED_PROPERTIES) {
mPropertyService.registerListener(propertyId, PROPERTY_UPDATE_RATE,
mICarPropertyEventListener);
}
}
// Binder methods
/**
* Register a {@link ICarDrivingStateChangeListener} to be notified for changes to the driving
* state.
*
* @param listener {@link ICarDrivingStateChangeListener}
*/
@Override
public void registerDrivingStateChangeListener(ICarDrivingStateChangeListener listener) {
if (listener == null) {
if (DBG) {
Slog.e(TAG, "registerDrivingStateChangeListener(): listener null");
}
throw new IllegalArgumentException("Listener is null");
}
mDrivingStateClients.register(listener);
}
/**
* Unregister the given Driving State Change listener
*
* @param listener client to unregister
*/
@Override
public void unregisterDrivingStateChangeListener(ICarDrivingStateChangeListener listener) {
if (listener == null) {
Slog.e(TAG, "unregisterDrivingStateChangeListener(): listener null");
throw new IllegalArgumentException("Listener is null");
}
mDrivingStateClients.unregister(listener);
}
/**
* Gets the current driving state
*
* @return {@link CarDrivingStateEvent} for the given event type
*/
@Override
@Nullable
public CarDrivingStateEvent getCurrentDrivingState() {
synchronized (mLock) {
return mCurrentDrivingState;
}
}
@Override
public void injectDrivingState(CarDrivingStateEvent event) {
ICarImpl.assertPermission(mContext, Car.PERMISSION_CONTROL_APP_BLOCKING);
dispatchEventToClients(event);
}
private void dispatchEventToClients(CarDrivingStateEvent event) {
boolean success = mClientDispatchHandler.post(() -> {
int numClients = mDrivingStateClients.beginBroadcast();
for (int i = 0; i < numClients; i++) {
ICarDrivingStateChangeListener callback = mDrivingStateClients.getBroadcastItem(i);
try {
callback.onDrivingStateChanged(event);
} catch (RemoteException e) {
Slog.e(TAG,
String.format("Dispatch to listener %s failed for event (%s)", callback,
event));
}
}
mDrivingStateClients.finishBroadcast();
});
if (!success) {
Slog.e(TAG, "Unable to post (" + event + ") event to dispatch handler");
}
}
@Override
public void dump(IndentingPrintWriter writer) {
writer.println("*CarDrivingStateService*");
mDrivingStateClients.dump(writer, "Driving State Clients ");
writer.println("Driving state change log:");
synchronized (mLock) {
for (Utils.TransitionLog tLog : mTransitionLogs) {
writer.println(tLog);
}
writer.println("Current Driving State: " + mCurrentDrivingState.eventValue);
if (mSupportedGears != null) {
writer.println("Supported gears:");
for (Integer gear : mSupportedGears) {
writer.print("Gear:" + gear);
}
}
}
}
/**
* {@link CarPropertyEvent} listener registered with the {@link CarPropertyService} for getting
* property change notifications.
*/
private final ICarPropertyEventListener mICarPropertyEventListener =
new ICarPropertyEventListener.Stub() {
@Override
public void onEvent(List<CarPropertyEvent> events) throws RemoteException {
synchronized (mLock) {
for (CarPropertyEvent event : events) {
handlePropertyEventLocked(event);
}
}
}
};
/**
* Handle events coming from {@link CarPropertyService}. Compute the driving state, map it to
* the corresponding UX Restrictions and dispatch the events to the registered clients.
*/
@VisibleForTesting
void handlePropertyEventLocked(CarPropertyEvent event) {
if (event.getEventType() != CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE) {
return;
}
CarPropertyValue value = event.getCarPropertyValue();
int propId = value.getPropertyId();
long curTimestamp = value.getTimestamp();
if (DBG) {
Slog.d(TAG, "Property Changed: propId=" + propId);
}
switch (propId) {
case VehicleProperty.PERF_VEHICLE_SPEED:
float curSpeed = (Float) value.getValue();
if (DBG) {
Slog.d(TAG, "Speed: " + curSpeed + "@" + curTimestamp);
}
if (curTimestamp > mLastSpeedTimestamp) {
mLastSpeedTimestamp = curTimestamp;
mLastSpeed = curSpeed;
} else if (DBG) {
Slog.d(TAG, "Ignoring speed with older timestamp:" + curTimestamp);
}
break;
case VehicleProperty.GEAR_SELECTION:
if (mSupportedGears == null) {
mSupportedGears = getSupportedGears();
}
int curGear = (Integer) value.getValue();
if (DBG) {
Slog.d(TAG, "Gear: " + curGear + "@" + curTimestamp);
}
if (curTimestamp > mLastGearTimestamp) {
mLastGearTimestamp = curTimestamp;
mLastGear = (Integer) value.getValue();
} else if (DBG) {
Slog.d(TAG, "Ignoring Gear with older timestamp:" + curTimestamp);
}
break;
case VehicleProperty.PARKING_BRAKE_ON:
boolean curParkingBrake = (boolean) value.getValue();
if (DBG) {
Slog.d(TAG, "Parking Brake: " + curParkingBrake + "@" + curTimestamp);
}
if (curTimestamp > mLastParkingBrakeTimestamp) {
mLastParkingBrakeTimestamp = curTimestamp;
mLastParkingBrakeState = curParkingBrake;
} else if (DBG) {
Slog.d(TAG, "Ignoring Parking Brake status with an older timestamp:"
+ curTimestamp);
}
break;
default:
Slog.e(TAG, "Received property event for unhandled propId=" + propId);
break;
}
int drivingState = inferDrivingStateLocked();
// Check if the driving state has changed. If it has, update our records and
// dispatch the new events to the listeners.
if (DBG) {
Slog.d(TAG, "Driving state new->old " + drivingState + "->"
+ mCurrentDrivingState.eventValue);
}
if (drivingState != mCurrentDrivingState.eventValue) {
addTransitionLogLocked(TAG, mCurrentDrivingState.eventValue, drivingState,
System.currentTimeMillis());
// Update if there is a change in state.
mCurrentDrivingState = createDrivingStateEvent(drivingState);
if (DBG) {
Slog.d(TAG, "dispatching to " + mDrivingStateClients.getRegisteredCallbackCount()
+ " clients");
}
// Dispatch to clients on a separate thread to prevent a deadlock
final CarDrivingStateEvent currentDrivingStateEvent = mCurrentDrivingState;
dispatchEventToClients(currentDrivingStateEvent);
}
}
private List<Integer> getSupportedGears() {
List<CarPropertyConfig> propertyList = mPropertyService
.getPropertyConfigList(REQUIRED_PROPERTIES);
for (CarPropertyConfig p : propertyList) {
if (p.getPropertyId() == VehicleProperty.GEAR_SELECTION) {
return p.getConfigArray();
}
}
return null;
}
@GuardedBy("mLock")
private void addTransitionLogLocked(String name, int from, int to, long timestamp) {
if (mTransitionLogs.size() >= MAX_TRANSITION_LOG_SIZE) {
mTransitionLogs.remove();
}
Utils.TransitionLog tLog = new Utils.TransitionLog(name, from, to, timestamp);
mTransitionLogs.add(tLog);
}
/**
* Infers the current driving state of the car from the other Car Sensor properties like
* Current Gear, Speed etc.
*
* @return Current driving state
*/
@GuardedBy("mLock")
@CarDrivingState
private int inferDrivingStateLocked() {
updateVehiclePropertiesIfNeededLocked();
if (DBG) {
Slog.d(TAG, "Last known Gear:" + mLastGear + " Last known speed:" + mLastSpeed);
}
/*
Logic to start off deriving driving state:
1. If gear == parked, then Driving State is parked.
2. If gear != parked,
2a. if parking brake is applied, then Driving state is parked.
2b. if parking brake is not applied or unknown/unavailable, then driving state
is still unknown.
3. If driving state is unknown at the end of step 2,
3a. if speed == 0, then driving state is idling
3b. if speed != 0, then driving state is moving
3c. if speed unavailable, then driving state is unknown
*/
if (isVehicleKnownToBeParkedLocked()) {
return CarDrivingStateEvent.DRIVING_STATE_PARKED;
}
// We don't know if the vehicle is parked, let's look at the speed.
if (mLastSpeedTimestamp == NOT_RECEIVED || mLastSpeed < 0) {
return CarDrivingStateEvent.DRIVING_STATE_UNKNOWN;
} else if (mLastSpeed == 0f) {
return CarDrivingStateEvent.DRIVING_STATE_IDLING;
} else {
return CarDrivingStateEvent.DRIVING_STATE_MOVING;
}
}
/**
* Find if we have signals to know if the vehicle is parked
*
* @return true if we have enough information to say the vehicle is parked.
* false, if the vehicle is either not parked or if we don't have any information.
*/
@GuardedBy("mLock")
private boolean isVehicleKnownToBeParkedLocked() {
// If we know the gear is in park, return true
if (mLastGearTimestamp != NOT_RECEIVED && mLastGear == VehicleGear.GEAR_PARK) {
return true;
} else if (mLastParkingBrakeTimestamp != NOT_RECEIVED) {
// if gear is not in park or unknown, look for status of parking brake if transmission
// type is manual.
if (isCarManualTransmissionTypeLocked()) {
return mLastParkingBrakeState;
}
}
// if neither information is available, return false to indicate we can't determine
// if the vehicle is parked.
return false;
}
/**
* If Supported gears information is available and GEAR_PARK is not one of the supported gears,
* transmission type is considered to be Manual. Automatic transmission is assumed otherwise.
*/
@GuardedBy("mLock")
private boolean isCarManualTransmissionTypeLocked() {
if (mSupportedGears != null
&& !mSupportedGears.isEmpty()
&& !mSupportedGears.contains(VehicleGear.GEAR_PARK)) {
return true;
}
return false;
}
/**
* Try querying the gear selection and parking brake if we haven't received the event yet.
* This could happen if the gear change occurred before car service booted up like in the
* case of a HU restart in the middle of a drive. Since gear and parking brake are
* on-change only properties, we could be in this situation where we will have to query
* VHAL.
*/
@GuardedBy("mLock")
private void updateVehiclePropertiesIfNeededLocked() {
if (mLastGearTimestamp == NOT_RECEIVED) {
CarPropertyValue propertyValue = mPropertyService.getPropertySafe(
VehicleProperty.GEAR_SELECTION,
VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL);
if (propertyValue != null) {
mLastGear = (Integer) propertyValue.getValue();
mLastGearTimestamp = propertyValue.getTimestamp();
if (DBG) {
Slog.d(TAG, "updateVehiclePropertiesIfNeeded: gear:" + mLastGear);
}
}
}
if (mLastParkingBrakeTimestamp == NOT_RECEIVED) {
CarPropertyValue propertyValue = mPropertyService.getPropertySafe(
VehicleProperty.PARKING_BRAKE_ON,
VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL);
if (propertyValue != null) {
mLastParkingBrakeState = (boolean) propertyValue.getValue();
mLastParkingBrakeTimestamp = propertyValue.getTimestamp();
if (DBG) {
Slog.d(TAG, "updateVehiclePropertiesIfNeeded: brake:" + mLastParkingBrakeState);
}
}
}
if (mLastSpeedTimestamp == NOT_RECEIVED) {
CarPropertyValue propertyValue = mPropertyService.getPropertySafe(
VehicleProperty.PERF_VEHICLE_SPEED,
VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL);
if (propertyValue != null) {
mLastSpeed = (float) propertyValue.getValue();
mLastSpeedTimestamp = propertyValue.getTimestamp();
if (DBG) {
Slog.d(TAG, "updateVehiclePropertiesIfNeeded: speed:" + mLastSpeed);
}
}
}
}
private static CarDrivingStateEvent createDrivingStateEvent(int eventValue) {
return new CarDrivingStateEvent(eventValue, SystemClock.elapsedRealtimeNanos());
}
}