Merge "Add intent action and extra for car media template app." into oc-mr1-dev
diff --git a/car-lib/src/android/car/vms/IVmsSubscriberService.aidl b/car-lib/src/android/car/vms/IVmsSubscriberService.aidl
index 9234134..fd610ce 100644
--- a/car-lib/src/android/car/vms/IVmsSubscriberService.aidl
+++ b/car-lib/src/android/car/vms/IVmsSubscriberService.aidl
@@ -24,40 +24,57 @@
  */
 interface IVmsSubscriberService {
     /**
-     * Subscribes the listener to receive messages from layer/version.
+     * Adds a subscriber to a VMS layer.
      */
-    void addVmsSubscriberClientListener(
-            in IVmsSubscriberClient listener,
+    void addVmsSubscriber(
+            in IVmsSubscriberClient subscriber,
             in VmsLayer layer) = 0;
 
     /**
-     * Subscribes the listener to receive messages from all published layer/version. The
-     * service will not send any subscription notifications to publishers (i.e. this is a passive
-     * subscriber).
+     * Adds a subscriber to all actively broadcasted layers.
+     * Publishers will not be notified regarding this request so the state of the service will not
+     * change.
      */
-    void addVmsSubscriberClientPassiveListener(in IVmsSubscriberClient listener) = 1;
+    void addVmsSubscriberPassive(in IVmsSubscriberClient subscriber) = 1;
 
     /**
-     * Tells the VmsSubscriberService a client unsubscribes to layer messages.
-     */
-    void removeVmsSubscriberClientListener(
-            in IVmsSubscriberClient listener,
-            in VmsLayer layer) = 2;
+     * Adds a subscriber to a VMS layer from a specific publisher.
+     */
+    void addVmsSubscriberToPublisher(
+            in IVmsSubscriberClient subscriber,
+            in VmsLayer layer,
+            int publisherId) = 2;
 
     /**
-     * Tells the VmsSubscriberService a passive client unsubscribes. This will not unsubscribe
-     * the listener from any specific layer it has subscribed to.
+     * Removes a subscriber to a VMS layer.
      */
-    void removeVmsSubscriberClientPassiveListener(
-            in IVmsSubscriberClient listener) = 3;
+    void removeVmsSubscriber(
+            in IVmsSubscriberClient subscriber,
+            in VmsLayer layer) = 3;
+
+    /**
+     * Removes a subscriber to all actively broadcasted layers.
+     * Publishers will not be notified regarding this request so the state of the service will not
+     * change.
+     */
+    void removeVmsSubscriberPassive(
+            in IVmsSubscriberClient subscriber) = 4;
+
+    /**
+     * Removes a subscriber to a VMS layer from a specific publisher.
+     */
+    void removeVmsSubscriberToPublisher(
+            in IVmsSubscriberClient subscriber,
+            in VmsLayer layer,
+            int publisherId) = 5;
 
     /**
      * Returns a list of available layers from the closure of the publishers offerings.
      */
-    List<VmsLayer> getAvailableLayers() = 4;
+    List<VmsLayer> getAvailableLayers() = 6;
 
     /**
      *  Returns a the publisher information for a publisher ID.
      */
-    byte[] getPublisherInfo(in int publisherId) = 5;
+    byte[] getPublisherInfo(in int publisherId) = 7;
 }
diff --git a/car-lib/src/android/car/vms/VmsOperationRecorder.java b/car-lib/src/android/car/vms/VmsOperationRecorder.java
index 6ddd6fa..7bac812 100644
--- a/car-lib/src/android/car/vms/VmsOperationRecorder.java
+++ b/car-lib/src/android/car/vms/VmsOperationRecorder.java
@@ -50,6 +50,14 @@
         recordOp("unsubscribe", layer);
     }
 
+    public void subscribe(VmsLayer layer, int publisherId) {
+        recordOp("subscribe", "publisherId", publisherId, layer);
+    }
+
+    public void unsubscribe(VmsLayer layer, int publisherId) {
+        recordOp("unsubscribe", "publisherId", publisherId, layer);
+    }
+
     public void subscribeAll() {
         recordOp("subscribeAll");
     }
diff --git a/car-lib/src/android/car/vms/VmsSubscriberManager.java b/car-lib/src/android/car/vms/VmsSubscriberManager.java
index 97447b9..a426f92 100644
--- a/car-lib/src/android/car/vms/VmsSubscriberManager.java
+++ b/car-lib/src/android/car/vms/VmsSubscriberManager.java
@@ -52,15 +52,23 @@
     @GuardedBy("mListenerLock")
     private VmsSubscriberClientListener mListener;
 
-    /** Interface exposed to VMS subscribers: it is a wrapper of IVmsSubscriberClient. */
+    /**
+     * Interface exposed to VMS subscribers: it is a wrapper of IVmsSubscriberClient.
+     */
     public interface VmsSubscriberClientListener {
-        /** Called when the property is updated */
+        /**
+         * Called when the property is updated
+         */
         void onVmsMessageReceived(VmsLayer layer, byte[] payload);
 
-        /** Called when layers availability change */
+        /**
+         * Called when layers availability change
+         */
         void onLayersAvailabilityChange(List<VmsLayer> availableLayers);
 
-        /** Notifies the client of the disconnect event */
+        /**
+         * Notifies the client of the disconnect event
+         */
         void onCarDisconnected();
     }
 
@@ -68,7 +76,9 @@
      * Allows to asynchronously dispatch onVmsMessageReceived events.
      */
     private final static class VmsEventHandler extends Handler {
-        /** Constants handled in the handler */
+        /**
+         * Constants handled in the handler
+         */
         private static final int ON_RECEIVE_MESSAGE_EVENT = 0;
         private static final int ON_AVAILABILITY_CHANGE_EVENT = 1;
 
@@ -140,16 +150,13 @@
      * {@link com.android.car.VmsSubscriberService} are done through the {@link #mIListener}.
      * Therefore, notifications from the {@link com.android.car.VmsSubscriberService} are received
      * by the {@link #mIListener} and then forwarded to the {@link #mListener}.
-     *
+     * <p>
      * It is expected that this method is invoked just once during the lifetime of the object.
      *
      * @param listener subscriber listener that will handle onVmsMessageReceived events.
      * @throws IllegalStateException if the listener was already set.
      */
     public void setListener(VmsSubscriberClientListener listener) {
-        if (DBG) {
-            Log.d(TAG, "Setting listener.");
-        }
         synchronized (mListenerLock) {
             if (mListener != null) {
                 throw new IllegalStateException("Listener is already configured.");
@@ -163,9 +170,6 @@
      */
     public byte[] getPublisherInfo(int publisherId)
             throws CarNotConnectedException, IllegalStateException {
-        if (DBG) {
-            Log.d(TAG, "Getting all publishers info.");
-        }
         try {
             return mVmsSubscriberService.getPublisherInfo(publisherId);
         } catch (RemoteException e) {
@@ -184,20 +188,9 @@
      * @throws IllegalStateException if the listener was not set via {@link #setListener}.
      */
     public void subscribe(VmsLayer layer) throws CarNotConnectedException {
-        if (DBG) {
-            Log.d(TAG, "Subscribing to layer: " + layer);
-        }
-        VmsSubscriberClientListener listener;
-        synchronized (mListenerLock) {
-            listener = mListener;
-        }
-        if (listener == null) {
-            Log.w(TAG, "subscribe: listener was not set, " +
-                    "setListener must be called first.");
-            throw new IllegalStateException("Listener was not set.");
-        }
+        verifySubscriptionIsAllowed();
         try {
-            mVmsSubscriberService.addVmsSubscriberClientListener(mIListener, layer);
+            mVmsSubscriberService.addVmsSubscriber(mIListener, layer);
             VmsOperationRecorder.get().subscribe(layer);
         } catch (RemoteException e) {
             Log.e(TAG, "Could not connect: ", e);
@@ -207,21 +200,30 @@
         }
     }
 
-    public void subscribeAll() throws CarNotConnectedException {
-        if (DBG) {
-            Log.d(TAG, "Subscribing passively to all data messages");
-        }
-        VmsSubscriberClientListener listener;
-        synchronized (mListenerLock) {
-            listener = mListener;
-        }
-        if (listener == null) {
-            Log.w(TAG, "subscribe: listener was not set, " +
-                    "setListener must be called first.");
-            throw new IllegalStateException("Listener was not set.");
-        }
+    /**
+     * Subscribes to listen to the layer specified from the publisher specified.
+     *
+     * @param layer       the layer to subscribe to.
+     * @param publisherId the publisher of the layer.
+     * @throws IllegalStateException if the listener was not set via {@link #setListener}.
+     */
+    public void subscribe(VmsLayer layer, int publisherId) throws CarNotConnectedException {
+        verifySubscriptionIsAllowed();
         try {
-            mVmsSubscriberService.addVmsSubscriberClientPassiveListener(mIListener);
+            mVmsSubscriberService.addVmsSubscriberToPublisher(mIListener, layer, publisherId);
+            VmsOperationRecorder.get().subscribe(layer, publisherId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not connect: ", e);
+            throw new CarNotConnectedException(e);
+        } catch (IllegalStateException ex) {
+            Car.checkCarNotConnectedExceptionFromCarService(ex);
+        }
+    }
+
+    public void subscribeAll() throws CarNotConnectedException {
+        verifySubscriptionIsAllowed();
+        try {
+            mVmsSubscriberService.addVmsSubscriberPassive(mIListener);
             VmsOperationRecorder.get().subscribeAll();
         } catch (RemoteException e) {
             Log.e(TAG, "Could not connect: ", e);
@@ -238,20 +240,9 @@
      * @throws IllegalStateException if the listener was not set via {@link #setListener}.
      */
     public void unsubscribe(VmsLayer layer) {
-        if (DBG) {
-            Log.d(TAG, "Unsubscribing from layer: " + layer);
-        }
-        VmsSubscriberClientListener listener;
-        synchronized (mListenerLock) {
-            listener = mListener;
-        }
-        if (listener == null) {
-            Log.w(TAG, "unsubscribe: listener was not set, " +
-                    "setListener must be called first.");
-            throw new IllegalStateException("Listener was not set.");
-        }
+        verifySubscriptionIsAllowed();
         try {
-            mVmsSubscriberService.removeVmsSubscriberClientListener(mIListener, layer);
+            mVmsSubscriberService.removeVmsSubscriber(mIListener, layer);
             VmsOperationRecorder.get().unsubscribe(layer);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to unregister subscriber", e);
@@ -261,21 +252,29 @@
         }
     }
 
-    public void unsubscribeAll() {
-        if (DBG) {
-            Log.d(TAG, "Unsubscribing passively from all data messages");
-        }
-        VmsSubscriberClientListener listener;
-        synchronized (mListenerLock) {
-            listener = mListener;
-        }
-        if (listener == null) {
-            Log.w(TAG, "unsubscribeAll: listener was not set, " +
-                    "setListener must be called first.");
-            throw new IllegalStateException("Listener was not set.");
-        }
+    /**
+     * Unsubscribes from the layer/version specified.
+     *
+     * @param layer       the layer to unsubscribe from.
+     * @param publisherId the pubisher of the layer.
+     * @throws IllegalStateException if the listener was not set via {@link #setListener}.
+     */
+    public void unsubscribe(VmsLayer layer, int publisherId) {
         try {
-            mVmsSubscriberService.removeVmsSubscriberClientPassiveListener(mIListener);
+            mVmsSubscriberService.removeVmsSubscriberToPublisher(
+                    mIListener, layer, publisherId);
+            VmsOperationRecorder.get().unsubscribe(layer, publisherId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to unregister subscriber", e);
+            // ignore
+        } catch (IllegalStateException ex) {
+            Car.hideCarNotConnectedExceptionFromCarService(ex);
+        }
+    }
+
+    public void unsubscribeAll() {
+        try {
+            mVmsSubscriberService.removeVmsSubscriberPassive(mIListener);
             VmsOperationRecorder.get().unsubscribeAll();
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to unregister subscriber ", e);
@@ -286,30 +285,47 @@
     }
 
     private void dispatchOnReceiveMessage(VmsLayer layer, byte[] payload) {
-        VmsSubscriberClientListener listener;
-        synchronized (mListenerLock) {
-            listener = mListener;
-        }
+        VmsSubscriberClientListener listener = getListenerThreadSafe();
         if (listener == null) {
-            Log.e(TAG, "Listener died, not dispatching event.");
+            Log.e(TAG, "Cannot dispatch received message.");
             return;
         }
         listener.onVmsMessageReceived(layer, payload);
     }
 
     private void dispatchOnAvailabilityChangeMessage(List<VmsLayer> availableLayers) {
-        VmsSubscriberClientListener listener;
-        synchronized (mListenerLock) {
-            listener = mListener;
-        }
+        VmsSubscriberClientListener listener = getListenerThreadSafe();
         if (listener == null) {
-            Log.e(TAG, "Listener died, not dispatching event.");
+            Log.e(TAG, "Cannot dispatch availability change message.");
             return;
         }
         listener.onLayersAvailabilityChange(availableLayers);
     }
 
-    /** @hide */
+    private VmsSubscriberClientListener getListenerThreadSafe() {
+        VmsSubscriberClientListener listener;
+        synchronized (mListenerLock) {
+            listener = mListener;
+        }
+        if (listener == null) {
+            Log.e(TAG, "Listener not set.");
+        }
+        return listener;
+    }
+
+    /*
+     * Verifies that the subscriber is in a state where it is allowed to subscribe.
+     */
+    private void verifySubscriptionIsAllowed() {
+        VmsSubscriberClientListener listener = getListenerThreadSafe();
+        if (listener == null) {
+            throw new IllegalStateException("Cannot subscribe.");
+        }
+    }
+
+    /**
+     * @hide
+     */
     @Override
     public void onCarDisconnected() {
         VmsSubscriberClientListener listener;
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index d919d8f..08fe6d0 100644
--- a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -89,7 +89,7 @@
                 android:id="@+id/delete_button"
                 style="@style/NumPadKeyButton"
                 android:gravity="center_vertical"
-                android:src="@drawable/ic_backspace_24dp"
+                android:src="@drawable/ic_backspace_black_24dp"
                 android:clickable="true"
                 android:background="@drawable/ripple_drawable"
                 android:contentDescription="@string/keyboardview_keycode_delete"
diff --git a/service/src/com/android/car/CarAudioService.java b/service/src/com/android/car/CarAudioService.java
index d161883..3b871fa 100644
--- a/service/src/com/android/car/CarAudioService.java
+++ b/service/src/com/android/car/CarAudioService.java
@@ -429,6 +429,7 @@
         if (audioPolicy != null) {
             mAudioManager.unregisterAudioPolicyAsync(audioPolicy);
         }
+        mVolumeService.release();
     }
 
     public synchronized void setAudioContextChangeListener(Looper looper,
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index c412b97..5b01bcf 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -28,9 +28,9 @@
 import android.os.IBinder;
 import android.os.Process;
 import android.os.Trace;
-import android.util.BootTimingsTraceLog;
 import android.util.Log;
 import android.util.Slog;
+import android.util.TimingsTraceLog;
 import com.android.car.cluster.InstrumentClusterService;
 import com.android.car.hal.VehicleHal;
 import com.android.car.internal.FeatureConfiguration;
@@ -81,7 +81,7 @@
 
     private static final String TAG = "ICarImpl";
     private static final String VHAL_TIMING_TAG = "VehicleHalTiming";
-    private static final BootTimingsTraceLog mBootTiming = new BootTimingsTraceLog(VHAL_TIMING_TAG,
+    private static final TimingsTraceLog mBootTiming = new TimingsTraceLog(VHAL_TIMING_TAG,
         Trace.TRACE_TAG_HAL);
 
     /** Test only service. Populate it only when necessary. */
diff --git a/service/src/com/android/car/VmsSubscriberService.java b/service/src/com/android/car/VmsSubscriberService.java
index 85b312d..f5787b5 100644
--- a/service/src/com/android/car/VmsSubscriberService.java
+++ b/service/src/com/android/car/VmsSubscriberService.java
@@ -219,7 +219,7 @@
 
     // Implements IVmsService interface.
     @Override
-    public void addVmsSubscriberClientListener(IVmsSubscriberClient subscriber, VmsLayer layer) {
+    public void addVmsSubscriber(IVmsSubscriberClient subscriber, VmsLayer layer) {
         synchronized (mSubscriberServiceLock) {
             // Add the subscriber so it can subscribe.
             mMessageReceivedManager.add(subscriber);
@@ -230,7 +230,7 @@
     }
 
     @Override
-    public void removeVmsSubscriberClientListener(IVmsSubscriberClient subscriber, VmsLayer layer) {
+    public void removeVmsSubscriber(IVmsSubscriberClient subscriber, VmsLayer layer) {
         synchronized (mSubscriberServiceLock) {
             // Remove the subscription.
             mHal.removeSubscription(subscriber, layer);
@@ -243,7 +243,35 @@
     }
 
     @Override
-    public void addVmsSubscriberClientPassiveListener(IVmsSubscriberClient subscriber) {
+    public void addVmsSubscriberToPublisher(IVmsSubscriberClient subscriber,
+                                                  VmsLayer layer,
+                                                  int publisherId) {
+        synchronized (mSubscriberServiceLock) {
+            // Add the subscriber so it can subscribe.
+            mMessageReceivedManager.add(subscriber);
+
+            // Add the subscription for the layer.
+            mHal.addSubscription(subscriber, layer, publisherId);
+        }
+    }
+
+    @Override
+    public void removeVmsSubscriberToPublisher(IVmsSubscriberClient subscriber,
+                                                     VmsLayer layer,
+                                                     int publisherId) {
+        synchronized (mSubscriberServiceLock) {
+            // Remove the subscription.
+            mHal.removeSubscription(subscriber, layer, publisherId);
+
+            // Remove the subscriber if it has no more subscriptions.
+            if (!mHal.containsSubscriber(subscriber)) {
+                mMessageReceivedManager.remove(subscriber);
+            }
+        }
+    }
+
+    @Override
+    public void addVmsSubscriberPassive(IVmsSubscriberClient subscriber) {
         synchronized (mSubscriberServiceLock) {
             mMessageReceivedManager.add(subscriber);
             mHal.addSubscription(subscriber);
@@ -251,7 +279,7 @@
     }
 
     @Override
-    public void removeVmsSubscriberClientPassiveListener(IVmsSubscriberClient subscriber) {
+    public void removeVmsSubscriberPassive(IVmsSubscriberClient subscriber) {
         synchronized (mSubscriberServiceLock) {
             // Remove the subscription.
             mHal.removeSubscription(subscriber);
@@ -279,7 +307,7 @@
     // Implements VmsHalSubscriberListener interface
     @Override
     public void onDataMessage(VmsLayer layer, int publisherId, byte[] payload) {
-        if(DBG) {
+        if (DBG) {
             Log.d(TAG, "Publishing a message for layer: " + layer);
         }
 
@@ -299,7 +327,7 @@
 
     @Override
     public void onLayersAvaiabilityChange(List<VmsAssociatedLayer> availableLayers) {
-        if(DBG) {
+        if (DBG) {
             Log.d(TAG, "Publishing layers availability change: " + availableLayers);
         }
 
diff --git a/service/src/com/android/car/hal/VmsHalService.java b/service/src/com/android/car/hal/VmsHalService.java
index 1defac1..7ff407f 100644
--- a/service/src/com/android/car/hal/VmsHalService.java
+++ b/service/src/com/android/car/hal/VmsHalService.java
@@ -144,7 +144,8 @@
             mRouting.addSubscription(listener, layer);
         }
         if (firstSubscriptionForLayer) {
-            notifyPublishers(layer, true);
+            notifyHalPublishers(layer, true);
+            notifyClientPublishers();
         }
     }
 
@@ -163,7 +164,8 @@
             layerHasSubscribers = mRouting.hasLayerSubscriptions(layer);
         }
         if (!layerHasSubscribers) {
-            notifyPublishers(layer, false);
+            notifyHalPublishers(layer, false);
+            notifyClientPublishers();
         }
     }
 
@@ -179,6 +181,44 @@
         }
     }
 
+    public void addSubscription(IVmsSubscriberClient listener, VmsLayer layer, int publisherId) {
+        boolean firstSubscriptionForLayer = false;
+        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(listener, layer, publisherId);
+        }
+        if (firstSubscriptionForLayer) {
+            notifyHalPublishers(layer, true);
+            notifyClientPublishers();
+        }
+    }
+
+    public void removeSubscription(IVmsSubscriberClient listener, VmsLayer layer, int publisherId) {
+        boolean layerHasSubscribers = true;
+        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(listener, layer, publisherId);
+
+            // Check if publishers need to be notified about this change in subscriptions.
+            layerHasSubscribers = mRouting.hasLayerSubscriptions(layer) ||
+                    mRouting.hasLayerFromPublisherSubscriptions(layer, publisherId);
+        }
+        if (!layerHasSubscribers) {
+            notifyHalPublishers(layer, false);
+            notifyClientPublishers();
+        }
+    }
+
     public void removeDeadSubscriber(IVmsSubscriberClient listener) {
         synchronized (mLock) {
             mRouting.removeDeadSubscriber(listener);
@@ -234,7 +274,7 @@
         }
     }
 
-    public void addHalSubscription(VmsLayer layer) {
+    private void addHalSubscription(VmsLayer layer) {
         boolean firstSubscriptionForLayer = true;
         synchronized (mLock) {
             // Check if publishers need to be notified about this change in subscriptions.
@@ -244,11 +284,28 @@
             mRouting.addHalSubscription(layer);
         }
         if (firstSubscriptionForLayer) {
-            notifyPublishers(layer, true);
+            notifyHalPublishers(layer, true);
+            notifyClientPublishers();
         }
     }
 
-    public void removeHalSubscription(VmsLayer layer) {
+    private void addHalSubscriptionToPublisher(VmsLayer layer, int publisherId) {
+        boolean firstSubscriptionForLayer = true;
+        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.addHalSubscriptionToPublisher(layer, publisherId);
+        }
+        if (firstSubscriptionForLayer) {
+            notifyHalPublishers(layer, publisherId, true);
+            notifyClientPublishers();
+        }
+    }
+
+    private void removeHalSubscription(VmsLayer layer) {
         boolean layerHasSubscribers = true;
         synchronized (mLock) {
             if (!mRouting.hasLayerSubscriptions(layer)) {
@@ -263,7 +320,29 @@
             layerHasSubscribers = mRouting.hasLayerSubscriptions(layer);
         }
         if (!layerHasSubscribers) {
-            notifyPublishers(layer, false);
+            notifyHalPublishers(layer, false);
+            notifyClientPublishers();
+        }
+    }
+
+    public void removeHalSubscriptionFromPublisher(VmsLayer layer, int publisherId) {
+        boolean layerHasSubscribers = true;
+        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.removeHalSubscriptionToPublisher(layer, publisherId);
+
+            // Check if publishers need to be notified about this change in subscriptions.
+            layerHasSubscribers = mRouting.hasLayerSubscriptions(layer) ||
+                    mRouting.hasLayerFromPublisherSubscriptions(layer, publisherId);
+        }
+        if (!layerHasSubscribers) {
+            notifyHalPublishers(layer, publisherId, false);
+            notifyClientPublishers();
         }
     }
 
@@ -297,10 +376,17 @@
      * @param layer          layer which is being subscribed to or unsubscribed from.
      * @param hasSubscribers indicates if the notification is for subscription or unsubscription.
      */
-    private void notifyPublishers(VmsLayer layer, boolean hasSubscribers) {
+    private void notifyHalPublishers(VmsLayer layer, boolean hasSubscribers) {
         // notify the HAL
         setSubscriptionRequest(layer, hasSubscribers);
+    }
 
+    private void notifyHalPublishers(VmsLayer layer, int publisherId, boolean hasSubscribers) {
+        // notify the HAL
+        setSubscriptionToPublisherRequest(layer, publisherId, hasSubscribers);
+    }
+
+    private void notifyClientPublishers() {
         // Notify the App publishers
         for (VmsHalPublisherListener listener : mPublisherListeners) {
             // Besides the list of layers, also a timestamp is provided to the clients.
@@ -390,6 +476,12 @@
                 case VmsMessageType.UNSUBSCRIBE:
                     handleUnsubscribeEvent(vec);
                     break;
+                case VmsMessageType.SUBSCRIBE_TO_PUBLISHER:
+                    handleSubscribeToPublisherEvent(vec);
+                    break;
+                case VmsMessageType.UNSUBSCRIBE_TO_PUBLISHER:
+                    handleUnsubscribeFromPublisherEvent(vec);
+                    break;
                 case VmsMessageType.OFFERING:
                     handleOfferingEvent(vec);
                     break;
@@ -450,6 +542,7 @@
      * <li>Message type.
      * <li>Layer id.
      * <li>Layer version.
+     * <li>Layer subtype.
      * </ul>
      */
     private void handleSubscribeEvent(List<Integer> integerValues) {
@@ -461,6 +554,27 @@
     }
 
     /**
+     * Subscribe message format:
+     * <ul>
+     * <li>Message type.
+     * <li>Layer id.
+     * <li>Layer version.
+     * <li>Layer subtype.
+     * <li>Publisher ID
+     * </ul>
+     */
+    private void handleSubscribeToPublisherEvent(List<Integer> integerValues) {
+        VmsLayer vmsLayer = parseVmsLayerFromSimpleMessageIntegerValues(integerValues);
+        if (DBG) {
+            Log.d(TAG, "Handling a subscribe event for Layer: " + vmsLayer);
+        }
+        int publisherId =
+                //integerValues.get(/*VmsSimpleMessageIntegerValuesIndex.VMS_LAYER_SUB_TYPE*/0);
+                integerValues.get(VmsMessageWithLayerAndPublisherIdIntegerValuesIndex.PUBLISHER_ID);
+        addHalSubscriptionToPublisher(vmsLayer, publisherId);
+    }
+
+    /**
      * Unsubscribe message format:
      * <ul>
      * <li>Message type.
@@ -476,6 +590,25 @@
         removeHalSubscription(vmsLayer);
     }
 
+    /**
+     * Unsubscribe message format:
+     * <ul>
+     * <li>Message type.
+     * <li>Layer id.
+     * <li>Layer version.
+     * </ul>
+     */
+    private void handleUnsubscribeFromPublisherEvent(List<Integer> integerValues) {
+        VmsLayer vmsLayer = parseVmsLayerFromSimpleMessageIntegerValues(integerValues);
+        int publisherId =
+                //integerValues.get(/*VmsSimpleMessageIntegerValuesIndex.VMS_LAYER_SUB_TYPE*/0);
+                integerValues.get(VmsMessageWithLayerAndPublisherIdIntegerValuesIndex.PUBLISHER_ID);
+        if (DBG) {
+            Log.d(TAG, "Handling an unsubscribe event for Layer: " + vmsLayer);
+        }
+        removeHalSubscriptionFromPublisher(vmsLayer, publisherId);
+    }
+
     private static int NUM_INTEGERS_IN_VMS_LAYER = 3;
 
     private VmsLayer parseVmsLayerFromIndex(List<Integer> integerValues, int index) {
@@ -622,6 +755,17 @@
         return setPropertyValue(vehiclePropertyValue);
     }
 
+    public boolean setSubscriptionToPublisherRequest(VmsLayer layer,
+                                                     int publisherId,
+                                                     boolean hasSubscribers) {
+        VehiclePropValue vehiclePropertyValue = toTypedVmsVehiclePropValueWithLayer(
+                hasSubscribers ?
+                        VmsMessageType.SUBSCRIBE_TO_PUBLISHER :
+                        VmsMessageType.UNSUBSCRIBE_TO_PUBLISHER, layer);
+        vehiclePropertyValue.value.int32Values.add(publisherId);
+        return setPropertyValue(vehiclePropertyValue);
+    }
+
     public boolean setDataMessage(VmsLayer layer, byte[] payload) {
         VehiclePropValue vehiclePropertyValue =
                 toTypedVmsVehiclePropValueWithLayer(VmsMessageType.DATA, layer);
diff --git a/tests/carservice_test/src/com/android/car/test/VmsOperationRecorderTest.java b/tests/carservice_test/src/com/android/car/test/VmsOperationRecorderTest.java
index a45d9df..dc42787 100644
--- a/tests/carservice_test/src/com/android/car/test/VmsOperationRecorderTest.java
+++ b/tests/carservice_test/src/com/android/car/test/VmsOperationRecorderTest.java
@@ -33,7 +33,9 @@
 @MediumTest
 public class VmsOperationRecorderTest extends TestCase {
 
-    /** Capture messages that VmsOperationRecorder.Writer would normally pass to Log.d(...). */
+    /**
+     * Capture messages that VmsOperationRecorder.Writer would normally pass to Log.d(...).
+     */
     class TestWriter extends VmsOperationRecorder.Writer {
         public String mMsg;
 
@@ -157,6 +159,18 @@
                 + "{'layer':{'id':3,'version':4,'subtype':5}}]}}");
     }
 
+    public void testSubscribeToPublisher() throws Exception {
+        mRecorder.subscribe(layer1, 99);
+        assertJsonMsgEquals(
+                "{'subscribe':{'publisherId':99, 'layer':{'id':1,'version':2,'subtype':3}}}");
+    }
+
+    public void testUnsubscribeToPublisher() throws Exception {
+        mRecorder.unsubscribe(layer1, 99);
+        assertJsonMsgEquals(
+                "{'unsubscribe':{'publisherId':99, 'layer':{'id':1,'version':2,'subtype':3}}}}");
+    }
+
     private void assertJsonMsgEquals(String expectJson) throws Exception {
         // Escaping double quotes in a JSON string is really noisy. The test data uses single
         // quotes instead, which gets replaced here.
diff --git a/tests/carservice_test/src/com/android/car/test/VmsSubscriberManagerTest.java b/tests/carservice_test/src/com/android/car/test/VmsSubscriberManagerTest.java
index b8d904a..c8dfb9c 100644
--- a/tests/carservice_test/src/com/android/car/test/VmsSubscriberManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/test/VmsSubscriberManagerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.car.test;
 
+import static org.junit.Assume.assumeTrue;
+
 import android.car.Car;
 import android.car.VehicleAreaType;
 import android.car.annotation.FutureFeature;
@@ -47,6 +49,7 @@
 public class VmsSubscriberManagerTest extends MockedCarTestBase {
     private static final String TAG = "VmsSubscriberManagerTest";
     private static final int PUBLISHER_ID = 17;
+    private static final int WRONG_PUBLISHER_ID = 26;
     private static final Set<Integer> PUBLISHERS_LIST = new HashSet<Integer>(Arrays.asList(PUBLISHER_ID));
 
     private static final int SUBSCRIPTION_LAYER_ID = 2;
@@ -113,7 +116,7 @@
 
     // Test injecting a value in the HAL and verifying it propagates to a subscriber.
     public void testSubscribe() throws Exception {
-        if (!VmsTestUtils.canRunTest(TAG)) return;
+        assumeTrue(VmsTestUtils.canRunTest(TAG));
         VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
                 Car.VMS_SUBSCRIBER_SERVICE);
         TestListener listener = new TestListener();
@@ -141,9 +144,153 @@
         assertTrue(Arrays.equals(expectedPayload, listener.getPayload()));
     }
 
+
+    // Test injecting a value in the HAL and verifying it propagates to a subscriber.
+    public void testSubscribeToPublisher() throws Exception {
+        assumeTrue(VmsTestUtils.canRunTest(TAG));
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+                Car.VMS_SUBSCRIBER_SERVICE);
+        TestListener listener = new TestListener();
+        vmsSubscriberManager.setListener(listener);
+        vmsSubscriberManager.subscribe(SUBSCRIPTION_LAYER, PUBLISHER_ID);
+
+        // Inject a value and wait for its callback in TestListener.onVmsMessageReceived.
+        VehiclePropValue v = VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE)
+                .setAreaId(VehicleAreaType.VEHICLE_AREA_TYPE_NONE)
+                .setTimestamp(SystemClock.elapsedRealtimeNanos())
+                .build();
+        v.value.int32Values.add(VmsMessageType.DATA); // MessageType
+        v.value.int32Values.add(SUBSCRIPTION_LAYER_ID);
+        v.value.int32Values.add(MOCK_PUBLISHER_LAYER_SUB_TYPE);
+        v.value.int32Values.add(SUBSCRIPTION_LAYER_VERSION);
+        v.value.int32Values.add(WRONG_PUBLISHER_ID);
+        v.value.bytes.add((byte) 0xa);
+        v.value.bytes.add((byte) 0xb);
+        assertEquals(0, mSubscriberSemaphore.availablePermits());
+
+        getMockedVehicleHal().injectEvent(v);
+
+        assertFalse(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
+    }
+
+    // Test injecting a value in the HAL and verifying it propagates to a subscriber.
+    public void testSubscribeFromPublisher() throws Exception {
+        assumeTrue(VmsTestUtils.canRunTest(TAG));
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+                Car.VMS_SUBSCRIBER_SERVICE);
+        TestListener listener = new TestListener();
+        vmsSubscriberManager.setListener(listener);
+        vmsSubscriberManager.subscribe(SUBSCRIPTION_LAYER, PUBLISHER_ID);
+
+        // Inject a value and wait for its callback in TestListener.onVmsMessageReceived.
+        VehiclePropValue v = VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE)
+                .setAreaId(VehicleAreaType.VEHICLE_AREA_TYPE_NONE)
+                .setTimestamp(SystemClock.elapsedRealtimeNanos())
+                .build();
+        v.value.int32Values.add(VmsMessageType.DATA); // MessageType
+        v.value.int32Values.add(SUBSCRIPTION_LAYER_ID);
+        v.value.int32Values.add(MOCK_PUBLISHER_LAYER_SUB_TYPE); //<-
+        v.value.int32Values.add(SUBSCRIPTION_LAYER_VERSION);
+        v.value.int32Values.add(PUBLISHER_ID);
+        v.value.bytes.add((byte) 0xa);
+        v.value.bytes.add((byte) 0xb);
+        assertEquals(0, mSubscriberSemaphore.availablePermits());
+
+        getMockedVehicleHal().injectEvent(v);
+        assertTrue(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
+        assertEquals(SUBSCRIPTION_LAYER, listener.getLayer());
+        byte[] expectedPayload = {(byte) 0xa, (byte) 0xb};
+        assertTrue(Arrays.equals(expectedPayload, listener.getPayload()));
+    }
+
+    // Test injecting a value in the HAL and verifying it does not propagate to a subscriber.
+    public void testUnsubscribe() throws Exception {
+        assumeTrue(VmsTestUtils.canRunTest(TAG));
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+                Car.VMS_SUBSCRIBER_SERVICE);
+        TestListener listener = new TestListener();
+        vmsSubscriberManager.setListener(listener);
+        vmsSubscriberManager.subscribe(SUBSCRIPTION_LAYER);
+        vmsSubscriberManager.unsubscribe(SUBSCRIPTION_LAYER);
+
+        // Inject a value and wait for its callback in TestListener.onVmsMessageReceived.
+        VehiclePropValue v = VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE)
+                .setAreaId(VehicleAreaType.VEHICLE_AREA_TYPE_NONE)
+                .setTimestamp(SystemClock.elapsedRealtimeNanos())
+                .build();
+        v.value.int32Values.add(VmsMessageType.DATA); // MessageType
+        v.value.int32Values.add(SUBSCRIPTION_LAYER_ID);
+        v.value.int32Values.add(MOCK_PUBLISHER_LAYER_SUB_TYPE);
+        v.value.int32Values.add(SUBSCRIPTION_LAYER_VERSION);
+        v.value.int32Values.add(PUBLISHER_ID);
+        v.value.bytes.add((byte) 0xa);
+        v.value.bytes.add((byte) 0xb);
+        assertEquals(0, mSubscriberSemaphore.availablePermits());
+
+        getMockedVehicleHal().injectEvent(v);
+        assertFalse(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
+    }
+
+    // Test injecting a value in the HAL and verifying it does not propagate to a subscriber.
+    public void testSubscribeFromWrongPublisher() throws Exception {
+        assumeTrue(VmsTestUtils.canRunTest(TAG));
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+                Car.VMS_SUBSCRIBER_SERVICE);
+        TestListener listener = new TestListener();
+        vmsSubscriberManager.setListener(listener);
+        vmsSubscriberManager.subscribe(SUBSCRIPTION_LAYER, PUBLISHER_ID);
+
+        // Inject a value and wait for its callback in TestListener.onVmsMessageReceived.
+        VehiclePropValue v = VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE)
+                .setAreaId(VehicleAreaType.VEHICLE_AREA_TYPE_NONE)
+                .setTimestamp(SystemClock.elapsedRealtimeNanos())
+                .build();
+        v.value.int32Values.add(VmsMessageType.DATA); // MessageType
+        v.value.int32Values.add(SUBSCRIPTION_LAYER_ID);
+        v.value.int32Values.add(MOCK_PUBLISHER_LAYER_SUB_TYPE);
+        v.value.int32Values.add(SUBSCRIPTION_LAYER_VERSION);
+        v.value.int32Values.add(WRONG_PUBLISHER_ID);
+        v.value.bytes.add((byte) 0xa);
+        v.value.bytes.add((byte) 0xb);
+        assertEquals(0, mSubscriberSemaphore.availablePermits());
+
+        getMockedVehicleHal().injectEvent(v);
+        assertFalse(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
+    }
+
+    // Test injecting a value in the HAL and verifying it does not propagate to a subscriber.
+    public void testUnsubscribeFromPublisher() throws Exception {
+        assumeTrue(VmsTestUtils.canRunTest(TAG));
+        VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+                Car.VMS_SUBSCRIBER_SERVICE);
+        TestListener listener = new TestListener();
+        vmsSubscriberManager.setListener(listener);
+        vmsSubscriberManager.subscribe(SUBSCRIPTION_LAYER, PUBLISHER_ID);
+        vmsSubscriberManager.unsubscribe(SUBSCRIPTION_LAYER, PUBLISHER_ID);
+
+        // Inject a value and wait for its callback in TestListener.onVmsMessageReceived.
+        VehiclePropValue v = VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE)
+                .setAreaId(VehicleAreaType.VEHICLE_AREA_TYPE_NONE)
+                .setTimestamp(SystemClock.elapsedRealtimeNanos())
+                .build();
+        v.value.int32Values.add(VmsMessageType.DATA); // MessageType
+        v.value.int32Values.add(SUBSCRIPTION_LAYER_ID);
+        v.value.int32Values.add(MOCK_PUBLISHER_LAYER_SUB_TYPE);
+        v.value.int32Values.add(SUBSCRIPTION_LAYER_VERSION);
+        v.value.int32Values.add(PUBLISHER_ID);
+        v.value.bytes.add((byte) 0xa);
+        v.value.bytes.add((byte) 0xb);
+        assertEquals(0, mSubscriberSemaphore.availablePermits());
+
+        getMockedVehicleHal().injectEvent(v);
+        assertFalse(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
+    }
+
+
+
     // Test injecting a value in the HAL and verifying it propagates to a subscriber.
     public void testSubscribeAll() throws Exception {
-        if (!VmsTestUtils.canRunTest(TAG)) return;
+        assumeTrue(VmsTestUtils.canRunTest(TAG));
         VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
                 Car.VMS_SUBSCRIBER_SERVICE);
         TestListener listener = new TestListener();
@@ -173,7 +320,7 @@
 
     // Test injecting a value in the HAL and verifying it propagates to a subscriber.
     public void testSimpleAvailableLayers() throws Exception {
-        if (!VmsTestUtils.canRunTest(TAG)) return;
+        assumeTrue(VmsTestUtils.canRunTest(TAG));
         VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
                 Car.VMS_SUBSCRIBER_SERVICE);
         TestListener listener = new TestListener();
@@ -219,7 +366,7 @@
 
     // Test injecting a value in the HAL and verifying it propagates to a subscriber.
     public void testComplexAvailableLayers() throws Exception {
-        if (!VmsTestUtils.canRunTest(TAG)) return;
+        assumeTrue(VmsTestUtils.canRunTest(TAG));
         VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
                 Car.VMS_SUBSCRIBER_SERVICE);
         TestListener listener = new TestListener();
diff --git a/tools/io_analysis/check_file_read.py b/tools/io_analysis/check_file_read.py
index 06b4f43..32c687e 100644
--- a/tools/io_analysis/check_file_read.py
+++ b/tools/io_analysis/check_file_read.py
@@ -42,11 +42,13 @@
     self.processes.append((open_time, process_name, flags))
     self.reads = []
     self.total_reads = 0
+    self.total_blocks = 0
     self.total_open = 1
     self.blocks = {}
     self.total_rereads = 0
     self.read_size_histogram = {} #key: read size, value: occurrence
     self.single_block_reads = {} # process name, occurrence
+    self.fresh_reads = [] # (offset, size)
 
   def add_open(self, open_time, process_name, flags):
     self.processes.append((open_time, process_name, flags))
@@ -55,9 +57,12 @@
   def add_read(self, time, offset, size, process_name):
     self.reads.append((time, offset, size, process_name))
     self.total_reads += size
+    already_read = True
     for i in range(offset, offset + size):
       if not self.blocks.get(i):
         self.blocks[i] = 1
+        already_read = False
+        self.total_blocks += 1
       else:
         self.blocks[i] += 1
         self.total_rereads += 1
@@ -65,6 +70,8 @@
       self.read_size_histogram[size] = 1
     else:
       self.read_size_histogram[size] += 1
+    if not already_read:
+      self.fresh_reads.append((offset, size))
     if size == 1:
       if not self.single_block_reads.get(process_name):
         self.single_block_reads[process_name] = 1
@@ -72,8 +79,9 @@
         self.single_block_reads[process_name] += 1
 
   def dump(self):
-    print " filename %s, total reads %d, total open %d total rereads %d inode %s" \
-      % (self.file_name, self.total_reads, self.total_open, self.total_rereads, self.inode)
+    print " filename %s, total reads %d, total open %d total rereads %d inode %s blocks %d" \
+      % (self.file_name, self.total_reads, self.total_open, self.total_rereads, self.inode, \
+         self.total_blocks)
     process_names = []
     for opener in self.processes:
       process_names.append(opener[1])
@@ -84,6 +92,7 @@
     if len(self.single_block_reads) > 1 and len(self.reads) > 1:
       print "  Single block reads:", collections.OrderedDict( \
         sorted(self.single_block_reads.items(), key = lambda item: item[1], reverse = True))
+    print "  Fresh reads:", self.fresh_reads
 
 class Trace:
   def __init__(self):
@@ -150,7 +159,7 @@
 
 
   def dump_partition(self, partition_name, files):
-    print "**Dump partition:", partition_name, "toal number of files:", len(files)
+    print "**Dump partition:", partition_name, "total number of files:", len(files)
     total_reads = 0
     total_rereads = 0
     vs = files.values()