| /* |
| * 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.hal; |
| |
| import static com.android.car.CarServiceUtils.toByteArray; |
| import static java.lang.Integer.toHexString; |
| |
| import android.car.VehicleAreaType; |
| import android.car.annotation.FutureFeature; |
| import android.car.vms.IOnVmsMessageReceivedListener; |
| import android.car.vms.VmsLayer; |
| import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig; |
| import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; |
| import android.hardware.automotive.vehicle.V2_1.VehicleProperty; |
| import android.hardware.automotive.vehicle.V2_1.VmsMessageIntegerValuesIndex; |
| import android.hardware.automotive.vehicle.V2_1.VmsMessageType; |
| import android.os.SystemClock; |
| import android.util.Log; |
| import com.android.car.CarLog; |
| import com.android.car.VmsRouting; |
| import com.android.internal.annotations.GuardedBy; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| |
| /** |
| * This is a glue layer between the VehicleHal and the VmsService. It sends VMS properties back and |
| * forth. |
| */ |
| @FutureFeature |
| public class VmsHalService extends HalServiceBase { |
| private static final boolean DBG = true; |
| private static final int HAL_PROPERTY_ID = VehicleProperty.VEHICLE_MAP_SERVICE; |
| private static final String TAG = "VmsHalService"; |
| private static final Set<Integer> SUPPORTED_MESSAGE_TYPES = |
| new HashSet<Integer>( |
| Arrays.asList( |
| VmsMessageType.SUBSCRIBE, |
| VmsMessageType.UNSUBSCRIBE, |
| VmsMessageType.DATA)); |
| |
| private boolean mIsSupported = false; |
| private CopyOnWriteArrayList<VmsHalPublisherListener> mPublisherListeners = |
| new CopyOnWriteArrayList<>(); |
| private CopyOnWriteArrayList<VmsHalSubscriberListener> mSubscriberListeners = |
| new CopyOnWriteArrayList<>(); |
| private final VehicleHal mVehicleHal; |
| @GuardedBy("mLock") |
| private VmsRouting mRouting = new VmsRouting(); |
| private final Object mLock = new Object(); |
| |
| /** |
| * The VmsPublisherService implements this interface to receive data from the HAL. |
| */ |
| public interface VmsHalPublisherListener { |
| void onChange(List<VmsLayer> layers, long sequence); |
| } |
| |
| /** |
| * The VmsSubscriberService implements this interface to receive data from the HAL. |
| */ |
| public interface VmsHalSubscriberListener { |
| void onChange(int layerId, int layerVersion, byte[] payload); |
| } |
| |
| /** |
| * The VmsService implements this interface to receive data from the HAL. |
| */ |
| protected VmsHalService(VehicleHal vehicleHal) { |
| mVehicleHal = vehicleHal; |
| if (DBG) { |
| Log.d(TAG, "started VmsHalService!"); |
| } |
| } |
| |
| public void addPublisherListener(VmsHalPublisherListener listener) { |
| mPublisherListeners.add(listener); |
| } |
| |
| public void addSubscriberListener(VmsHalSubscriberListener listener) { |
| mSubscriberListeners.add(listener); |
| } |
| |
| public void removePublisherListener(VmsHalPublisherListener listener) { |
| mPublisherListeners.remove(listener); |
| } |
| |
| public void removeSubscriberListener(VmsHalSubscriberListener listener) { |
| mSubscriberListeners.remove(listener); |
| } |
| |
| public void addSubscription(IOnVmsMessageReceivedListener listener, VmsLayer layer) { |
| synchronized (mLock) { |
| // Check if publishers need to be notified about this change in subscriptions. |
| boolean firstSubscriptionForLayer = !mRouting.getSubscribedLayers().contains(layer); |
| |
| // Add the listeners subscription to the layer |
| mRouting.addSubscription(listener, layer); |
| |
| // Notify the publishers |
| if (firstSubscriptionForLayer) { |
| notifyPublishers(layer, true); |
| } |
| } |
| } |
| |
| public void removeSubscription(IOnVmsMessageReceivedListener listener, VmsLayer layer) { |
| synchronized (mLock) { |
| if (!mRouting.hasLayerSubscriptions(layer)) { |
| Log.i(TAG, "Trying to remove a layer with no subscription: " + layer); |
| return; |
| } |
| |
| // Remove the listeners subscription to the layer |
| mRouting.removeSubscription(listener, layer); |
| |
| // Check if publishers need to be notified about this change in subscriptions. |
| boolean layerHasSubscribers = mRouting.getSubscribedLayers().contains(layer); |
| |
| // Notify the publishers |
| if (!layerHasSubscribers) { |
| notifyPublishers(layer, false); |
| } |
| } |
| } |
| |
| public void addSubscription(IOnVmsMessageReceivedListener listener) { |
| synchronized (mLock) { |
| mRouting.addSubscription(listener); |
| } |
| } |
| |
| public void removeSubscription(IOnVmsMessageReceivedListener listener) { |
| synchronized (mLock) { |
| mRouting.removeSubscription(listener); |
| } |
| } |
| |
| public void removeDeadListener(IOnVmsMessageReceivedListener listener) { |
| synchronized (mLock) { |
| mRouting.removeDeadListener(listener); |
| } |
| } |
| |
| public Set<IOnVmsMessageReceivedListener> getListeners(VmsLayer layer) { |
| return mRouting.getListeners(layer); |
| } |
| |
| public boolean isHalSubscribed(VmsLayer layer) { |
| return mRouting.isHalSubscribed(layer); |
| } |
| |
| public List<VmsLayer> getSubscribedLayers() { |
| return new ArrayList<>(mRouting.getSubscribedLayers()); |
| } |
| |
| public void addHalSubscription(VmsLayer layer) { |
| synchronized (mLock) { |
| // Check if publishers need to be notified about this change in subscriptions. |
| boolean firstSubscriptionForLayer = !mRouting.getSubscribedLayers().contains(layer); |
| |
| // Add the listeners subscription to the layer |
| mRouting.addHalSubscription(layer); |
| |
| if (firstSubscriptionForLayer) { |
| notifyPublishers(layer, true); |
| } |
| } |
| } |
| |
| public void removeHalSubscription(VmsLayer layer) { |
| synchronized (mLock) { |
| if (!mRouting.hasLayerSubscriptions(layer)) { |
| Log.i(TAG, "Trying to remove a layer with no subscription: " + layer); |
| return; |
| } |
| |
| // Remove the listeners subscription to the layer |
| mRouting.removeHalSubscription(layer); |
| |
| // Check if publishers need to be notified about this change in subscriptions. |
| boolean layerHasSubscribers = mRouting.getSubscribedLayers().contains(layer); |
| |
| // Notify the publishers |
| if (!layerHasSubscribers) { |
| notifyPublishers(layer, false); |
| } |
| } |
| } |
| |
| public boolean containsListener(IOnVmsMessageReceivedListener listener) { |
| return mRouting.containsListener(listener); |
| } |
| |
| /** |
| * Notify all the publishers and the HAL on subscription changes regardless of who triggered |
| * the change. |
| * |
| * @param layer layer which is being subscribed to or unsubscribed from. |
| * @param hasSubscribers indicates if the notification is for subscription or unsubscription. |
| */ |
| public void notifyPublishers(VmsLayer layer, boolean hasSubscribers) { |
| synchronized (mLock) { |
| // notify the HAL |
| setSubscriptionRequest(layer.getId(), layer.getVersion(), hasSubscribers); |
| |
| // Notify the App publishers |
| for (VmsHalPublisherListener listener : mPublisherListeners) { |
| // Besides the list of layers, also a timestamp is provided to the clients. |
| // They should ignore any notification with a timestamp that is older than the most |
| // recent timestamp they have seen. |
| listener.onChange(getSubscribedLayers(), SystemClock.elapsedRealtimeNanos()); |
| } |
| } |
| } |
| |
| @Override |
| public void init() { |
| if (DBG) { |
| Log.d(TAG, "init()"); |
| } |
| if (mIsSupported) { |
| mVehicleHal.subscribeProperty(this, HAL_PROPERTY_ID, 0); |
| } |
| } |
| |
| @Override |
| public void release() { |
| if (DBG) { |
| Log.d(TAG, "release()"); |
| } |
| if (mIsSupported) { |
| mVehicleHal.unsubscribeProperty(this, HAL_PROPERTY_ID); |
| } |
| mPublisherListeners.clear(); |
| mSubscriberListeners.clear(); |
| } |
| |
| @Override |
| public Collection<VehiclePropConfig> takeSupportedProperties( |
| Collection<VehiclePropConfig> allProperties) { |
| List<VehiclePropConfig> taken = new LinkedList<>(); |
| for (VehiclePropConfig p : allProperties) { |
| if (p.prop == HAL_PROPERTY_ID) { |
| taken.add(p); |
| mIsSupported = true; |
| if (DBG) { |
| Log.d(TAG, "takeSupportedProperties: " + toHexString(p.prop)); |
| } |
| break; |
| } |
| } |
| return taken; |
| } |
| |
| @Override |
| public void handleHalEvents(List<VehiclePropValue> values) { |
| if (DBG) { |
| Log.d(TAG, "Handling a VMS property change"); |
| } |
| for (VehiclePropValue v : values) { |
| ArrayList<Integer> vec = v.value.int32Values; |
| int messageType = vec.get(VmsMessageIntegerValuesIndex.VMS_MESSAGE_TYPE); |
| int layerId = vec.get(VmsMessageIntegerValuesIndex.VMS_LAYER_ID); |
| int layerVersion = vec.get(VmsMessageIntegerValuesIndex.VMS_LAYER_VERSION); |
| |
| // Check if message type is supported. |
| if (!SUPPORTED_MESSAGE_TYPES.contains(messageType)) { |
| throw new IllegalArgumentException("Unexpected message type. " + |
| "Expecting: " + SUPPORTED_MESSAGE_TYPES + |
| ". Got: " + messageType); |
| |
| } |
| |
| if (DBG) { |
| Log.d(TAG, |
| "Received message for Type: " + messageType + |
| " Layer Id: " + layerId + |
| "Version: " + layerVersion); |
| } |
| // This is a data message intended for subscribers. |
| if (messageType == VmsMessageType.DATA) { |
| // Get the payload. |
| byte[] payload = toByteArray(v.value.bytes); |
| |
| // Send the message. |
| for (VmsHalSubscriberListener listener : mSubscriberListeners) { |
| listener.onChange(layerId, layerVersion, payload); |
| } |
| } else if (messageType == VmsMessageType.SUBSCRIBE) { |
| addHalSubscription(new VmsLayer(layerId, layerVersion)); |
| } else { |
| // messageType == VmsMessageType.UNSUBSCRIBE |
| removeHalSubscription(new VmsLayer(layerId, layerVersion)); |
| } |
| } |
| } |
| |
| @Override |
| public void dump(PrintWriter writer) { |
| writer.println(TAG); |
| writer.println("VmsProperty " + (mIsSupported ? "" : "not") + " supported."); |
| } |
| |
| /** |
| * Updates the VMS HAL property with the given value. |
| * |
| * @param property the value used to update the HAL property. |
| * @return true if the call to the HAL to update the property was successful. |
| */ |
| public boolean setSubscriptionRequest(int layerId, int layerVersion, boolean hasSubscribers) { |
| VehiclePropValue vehiclePropertyValue = toVehiclePropValue( |
| hasSubscribers ? VmsMessageType.SUBSCRIBE : VmsMessageType.UNSUBSCRIBE, |
| layerId, |
| layerVersion); |
| return setPropertyValue(vehiclePropertyValue); |
| } |
| |
| public boolean setDataMessage(int layerId, int layerVersion, byte[] payload) { |
| VehiclePropValue vehiclePropertyValue = toVehiclePropValue(VmsMessageType.DATA, |
| layerId, |
| layerVersion, |
| payload); |
| return setPropertyValue(vehiclePropertyValue); |
| } |
| |
| public boolean setPropertyValue(VehiclePropValue vehiclePropertyValue) { |
| try { |
| mVehicleHal.set(vehiclePropertyValue); |
| return true; |
| } catch (PropertyTimeoutException e) { |
| Log.e(CarLog.TAG_PROPERTY, "set, property not ready 0x" + toHexString(HAL_PROPERTY_ID)); |
| } |
| return false; |
| } |
| |
| /** Creates a {@link VehiclePropValue} */ |
| static VehiclePropValue toVehiclePropValue(int messageType, |
| int layerId, |
| int layerVersion) { |
| VehiclePropValue vehicleProp = new VehiclePropValue(); |
| vehicleProp.prop = HAL_PROPERTY_ID; |
| vehicleProp.areaId = VehicleAreaType.VEHICLE_AREA_TYPE_NONE; |
| VehiclePropValue.RawValue v = vehicleProp.value; |
| |
| v.int32Values.add(messageType); |
| v.int32Values.add(layerId); |
| v.int32Values.add(layerVersion); |
| return vehicleProp; |
| } |
| |
| /** Creates a {@link VehiclePropValue} with payload*/ |
| static VehiclePropValue toVehiclePropValue(int messageType, |
| int layerId, |
| int layerVersion, |
| byte[] payload) { |
| VehiclePropValue vehicleProp = toVehiclePropValue(messageType, layerId, layerVersion); |
| VehiclePropValue.RawValue v = vehicleProp.value; |
| v.bytes.ensureCapacity(payload.length); |
| for (byte b : payload) { |
| v.bytes.add(b); |
| } |
| return vehicleProp; |
| } |
| } |