blob: 26626ff1d02ef0e13648247543e63af64193149d [file] [log] [blame]
/*
* Copyright (C) 2019 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.vms;
import android.car.vms.IVmsSubscriberClient;
import android.car.vms.VmsAvailableLayers;
import android.car.vms.VmsLayer;
import android.car.vms.VmsLayersOffering;
import android.car.vms.VmsOperationRecorder;
import android.car.vms.VmsSubscriptionState;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
import android.util.Log;
import com.android.car.VmsLayersAvailability;
import com.android.car.VmsPublishersInfo;
import com.android.car.VmsRouting;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.IntSupplier;
/**
* Broker service facilitating subscription handling and message passing between
* VmsPublisherService, VmsSubscriberService, and VmsHalService.
*/
public class VmsBrokerService {
private static final boolean DBG = true;
private static final String TAG = "VmsBrokerService";
@VisibleForTesting
static final String HAL_CLIENT = "HalClient";
@VisibleForTesting
static final String UNKNOWN_PACKAGE = "UnknownPackage";
private CopyOnWriteArrayList<PublisherListener> mPublisherListeners =
new CopyOnWriteArrayList<>();
private CopyOnWriteArrayList<SubscriberListener> mSubscriberListeners =
new CopyOnWriteArrayList<>();
private PackageManager mPackageManager;
private IntSupplier mGetCallingPid;
private IntSupplier mGetCallingUid;
private final Object mLock = new Object();
@GuardedBy("mLock")
private final VmsRouting mRouting = new VmsRouting();
@GuardedBy("mLock")
private final Map<IBinder, String> mBinderPackage = new HashMap<>();
@GuardedBy("mLock")
private final Map<IBinder, Map<Integer, VmsLayersOffering>> mOfferings = new HashMap<>();
@GuardedBy("mLock")
private final VmsLayersAvailability mAvailableLayers = new VmsLayersAvailability();
@GuardedBy("mLock")
private final VmsPublishersInfo mPublishersInfo = new VmsPublishersInfo();
/**
* The VMS publisher service implements this interface to receive publisher callbacks.
*/
public interface PublisherListener {
/**
* Callback triggered when publisher subscription state changes.
*
* @param subscriptionState Current subscription state.
*/
void onSubscriptionChange(VmsSubscriptionState subscriptionState);
}
/**
* The VMS subscriber service implements this interface to receive subscriber callbacks.
*/
public interface SubscriberListener {
/**
* Callback triggered when data is published for a given layer.
*
* @param layer Layer data is being published for
* @param publisherId Publisher of data
* @param payload Layer data
*/
void onMessageReceived(VmsLayer layer, int publisherId, byte[] payload);
/**
* Callback triggered when the layers available for subscription changes.
*
* @param availableLayers Current layer availability
*/
void onLayersAvailabilityChange(VmsAvailableLayers availableLayers);
}
/**
* Constructs new broker service.
*/
public VmsBrokerService(PackageManager packageManager) {
this(packageManager, Binder::getCallingPid, Binder::getCallingUid);
}
@VisibleForTesting
VmsBrokerService(PackageManager packageManager, IntSupplier getCallingPid,
IntSupplier getCallingUid) {
if (DBG) Log.d(TAG, "Started VmsBrokerService!");
mPackageManager = packageManager;
mGetCallingPid = getCallingPid;
mGetCallingUid = getCallingUid;
}
/**
* Adds a listener for publisher callbacks.
*
* @param listener Publisher callback listener
*/
public void addPublisherListener(PublisherListener listener) {
mPublisherListeners.add(listener);
}
/**
* Adds a listener for subscriber callbacks.
*
* @param listener Subscriber callback listener
*/
public void addSubscriberListener(SubscriberListener listener) {
mSubscriberListeners.add(listener);
}
/**
* Removes a listener for publisher callbacks.
*
* @param listener Publisher callback listener
*/
public void removePublisherListener(PublisherListener listener) {
mPublisherListeners.remove(listener);
}
/**
* Removes a listener for subscriber callbacks.
*
* @param listener Subscriber callback listener
*/
public void removeSubscriberListener(SubscriberListener listener) {
mSubscriberListeners.remove(listener);
}
/**
* Adds a subscription to all layers.
*
* @param subscriber Subscriber client to send layer data
*/
public void addSubscription(IVmsSubscriberClient subscriber) {
synchronized (mLock) {
mRouting.addSubscription(subscriber);
// Add mapping from binder to package name of subscriber.
mBinderPackage.computeIfAbsent(subscriber.asBinder(), k -> getCallingPackage());
}
}
/**
* Removes a subscription to all layers.
*
* @param subscriber Subscriber client to remove subscription for
*/
public void removeSubscription(IVmsSubscriberClient subscriber) {
synchronized (mLock) {
mRouting.removeSubscription(subscriber);
}
}
/**
* Adds a layer subscription.
*
* @param subscriber Subscriber client to send layer data
* @param layer Layer to send
*/
public void addSubscription(IVmsSubscriberClient subscriber, VmsLayer layer) {
boolean firstSubscriptionForLayer;
if (DBG) Log.d(TAG, "Checking for first subscription. Layer: " + layer);
synchronized (mLock) {
// Check if publishers need to be notified about this change in subscriptions.
firstSubscriptionForLayer = !mRouting.hasLayerSubscriptions(layer);
// Add the listeners subscription to the layer
mRouting.addSubscription(subscriber, layer);
// Add mapping from binder to package name of subscriber.
mBinderPackage.computeIfAbsent(subscriber.asBinder(), k -> getCallingPackage());
}
if (firstSubscriptionForLayer) {
notifyOfSubscriptionChange();
}
}
/**
* Removes a layer subscription.
*
* @param subscriber Subscriber client to remove subscription for
* @param layer Layer to remove
*/
public void removeSubscription(IVmsSubscriberClient subscriber, VmsLayer layer) {
boolean layerHasSubscribers;
synchronized (mLock) {
if (!mRouting.hasLayerSubscriptions(layer)) {
if (DBG) Log.d(TAG, "Trying to remove a layer with no subscription: " + layer);
return;
}
// Remove the listeners subscription to the layer
mRouting.removeSubscription(subscriber, layer);
// Check if publishers need to be notified about this change in subscriptions.
layerHasSubscribers = mRouting.hasLayerSubscriptions(layer);
}
if (!layerHasSubscribers) {
notifyOfSubscriptionChange();
}
}
/**
* Adds a publisher-specific layer subscription.
*
* @param subscriber Subscriber client to send layer data
* @param layer Layer to send
* @param publisherId Publisher of layer
*/
public void addSubscription(IVmsSubscriberClient subscriber, VmsLayer layer, int publisherId) {
boolean firstSubscriptionForLayer;
synchronized (mLock) {
// Check if publishers need to be notified about this change in subscriptions.
firstSubscriptionForLayer = !(mRouting.hasLayerSubscriptions(layer)
|| mRouting.hasLayerFromPublisherSubscriptions(layer, publisherId));
// Add the listeners subscription to the layer
mRouting.addSubscription(subscriber, layer, publisherId);
// Add mapping from binder to package name of subscriber.
mBinderPackage.computeIfAbsent(subscriber.asBinder(), k -> getCallingPackage());
}
if (firstSubscriptionForLayer) {
notifyOfSubscriptionChange();
}
}
/**
* Removes a publisher-specific layer subscription.
*
* @param subscriber Subscriber client to remove subscription for
* @param layer Layer to remove
* @param publisherId Publisher of layer
*/
public void removeSubscription(IVmsSubscriberClient subscriber, VmsLayer layer,
int publisherId) {
boolean layerHasSubscribers;
synchronized (mLock) {
if (!mRouting.hasLayerFromPublisherSubscriptions(layer, publisherId)) {
Log.i(TAG, "Trying to remove a layer with no subscription: "
+ layer + ", publisher ID:" + publisherId);
return;
}
// Remove the listeners subscription to the layer
mRouting.removeSubscription(subscriber, layer, publisherId);
// Check if publishers need to be notified about this change in subscriptions.
layerHasSubscribers = mRouting.hasLayerSubscriptions(layer)
|| mRouting.hasLayerFromPublisherSubscriptions(layer, publisherId);
}
if (!layerHasSubscribers) {
notifyOfSubscriptionChange();
}
}
/**
* Removes a disconnected subscriber's subscriptions
*
* @param subscriber Subscriber that was disconnected
*/
public void removeDeadSubscriber(IVmsSubscriberClient subscriber) {
boolean subscriptionStateChanged;
synchronized (mLock) {
subscriptionStateChanged = mRouting.removeDeadSubscriber(subscriber);
// Remove mapping from binder to package name of subscriber.
mBinderPackage.remove(subscriber.asBinder());
}
if (subscriptionStateChanged) {
notifyOfSubscriptionChange();
}
}
/**
* Gets all subscribers for a specific layer/publisher combination.
*
* @param layer Layer to query
* @param publisherId Publisher of layer
*/
public Set<IVmsSubscriberClient> getSubscribersForLayerFromPublisher(VmsLayer layer,
int publisherId) {
synchronized (mLock) {
return mRouting.getSubscribersForLayerFromPublisher(layer, publisherId);
}
}
/**
* Gets the state of all layer subscriptions.
*/
public VmsSubscriptionState getSubscriptionState() {
synchronized (mLock) {
return mRouting.getSubscriptionState();
}
}
/**
* Assigns an idempotent ID for publisherInfo and stores it. The idempotency in this case means
* that the same publisherInfo will always, within a trip of the vehicle, return the same ID.
* The publisherInfo should be static for a binary and should only change as part of a software
* update. The publisherInfo is a serialized proto message which VMS clients can interpret.
*/
public int getPublisherId(byte[] publisherInfo) {
if (DBG) Log.i(TAG, "Getting publisher static ID");
synchronized (mLock) {
return mPublishersInfo.getIdForInfo(publisherInfo);
}
}
/**
* Gets the publisher information data registered in {@link #getPublisherId(byte[])}
*
* @param publisherId Publisher ID to query
* @return Publisher information
*/
public byte[] getPublisherInfo(int publisherId) {
if (DBG) Log.i(TAG, "Getting information for publisher ID: " + publisherId);
synchronized (mLock) {
return mPublishersInfo.getPublisherInfo(publisherId);
}
}
/**
* Sets the layers offered by the publisher with the given publisher token.
*
* @param publisherToken Identifier token of publisher
* @param offering Layers offered by publisher
*/
public void setPublisherLayersOffering(IBinder publisherToken, VmsLayersOffering offering) {
synchronized (mLock) {
Map<Integer, VmsLayersOffering> publisherOfferings = mOfferings.computeIfAbsent(
publisherToken, k -> new HashMap<>());
publisherOfferings.put(offering.getPublisherId(), offering);
updateLayerAvailability();
}
VmsOperationRecorder.get().setPublisherLayersOffering(offering);
notifyOfAvailabilityChange();
}
/**
* Removes a disconnected publisher's offerings
*
* @param publisherToken Identifier token of publisher to be removed
*/
public void removeDeadPublisher(IBinder publisherToken) {
synchronized (mLock) {
mOfferings.remove(publisherToken);
updateLayerAvailability();
}
notifyOfAvailabilityChange();
}
/**
* Gets all layers available for subscription.
*
* @return All available layers
*/
public VmsAvailableLayers getAvailableLayers() {
synchronized (mLock) {
return mAvailableLayers.getAvailableLayers();
}
}
/**
* Gets the package name for a given IVmsSubscriberClient
*/
public String getPackageName(IVmsSubscriberClient subscriber) {
synchronized (mLock) {
return mBinderPackage.get(subscriber.asBinder());
}
}
private void updateLayerAvailability() {
Set<VmsLayersOffering> allPublisherOfferings = new HashSet<>();
synchronized (mLock) {
for (Map<Integer, VmsLayersOffering> offerings : mOfferings.values()) {
allPublisherOfferings.addAll(offerings.values());
}
if (DBG) Log.d(TAG, "New layer availability: " + allPublisherOfferings);
mAvailableLayers.setPublishersOffering(allPublisherOfferings);
}
}
private void notifyOfSubscriptionChange() {
if (DBG) Log.d(TAG, "Notifying publishers on subscriptions");
VmsSubscriptionState subscriptionState = getSubscriptionState();
// Notify the App publishers
for (PublisherListener listener : mPublisherListeners) {
listener.onSubscriptionChange(subscriptionState);
}
}
private void notifyOfAvailabilityChange() {
if (DBG) Log.d(TAG, "Notifying subscribers on layers availability");
VmsAvailableLayers availableLayers = getAvailableLayers();
// Notify the App subscribers
for (SubscriberListener listener : mSubscriberListeners) {
listener.onLayersAvailabilityChange(availableLayers);
}
}
// If we're in a binder call, returns back the package name of the caller of the binder call.
private String getCallingPackage() {
int callingPid = mGetCallingPid.getAsInt();
// Since the HAL lives in the same process, if the callingPid is equal to this process's
// PID, we know it's the HAL client.
if (callingPid == Process.myPid()) {
return HAL_CLIENT;
}
int callingUid = mGetCallingUid.getAsInt();
String packageName = mPackageManager.getNameForUid(callingUid);
if (packageName == null) {
return UNKNOWN_PACKAGE;
} else {
return packageName;
}
}
}