Made changes to VmsBrokerService and ICarImpl to string through PackageManager that
allows VmsPublisherService to retrieve a VMS publisher's java package
name. Moreover in VmsPublisherService:

1) Add in counters for packet counts/cumulative packet size by VmsLayer.
2) Add in counters for packet failures by the triple of VmsLayer, publisher
name and subscriber name.
3) Add in more lines to dump() to output metrics kept in counters

Test: Unit tests
Change-Id: I4c57b7d015e1ca4be91ac06dd008d0af3826e575
Bug: 131760375
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 244ee8b..cb18ff8 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -150,7 +150,7 @@
                 mAppFocusService, mCarInputService);
         mSystemStateControllerService = new SystemStateControllerService(
                 serviceContext, mCarAudioService, this);
-        mVmsBrokerService = new VmsBrokerService();
+        mVmsBrokerService = new VmsBrokerService(mContext.getPackageManager());
         mVmsClientManager = new VmsClientManager(
                 serviceContext, mCarUserService, mUserManagerHelper, mHal.getVmsHal());
         mVmsSubscriberService = new VmsSubscriberService(
diff --git a/service/src/com/android/car/VmsPublisherService.java b/service/src/com/android/car/VmsPublisherService.java
index 3d9a104..3384e3a 100644
--- a/service/src/com/android/car/VmsPublisherService.java
+++ b/service/src/com/android/car/VmsPublisherService.java
@@ -31,12 +31,16 @@
 
 import com.android.car.vms.VmsBrokerService;
 import com.android.car.vms.VmsClientManager;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
 import java.util.Collections;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
+
 /**
  * Receives HAL updates by implementing VmsHalService.VmsHalListener.
  * Binds to publishers and configures them to use this service.
@@ -46,12 +50,68 @@
     private static final boolean DBG = true;
     private static final String TAG = "VmsPublisherService";
 
+    @VisibleForTesting
+    static final String PACKET_COUNT_FORMAT = "Packet count for layer %s: %d\n";
+
+    @VisibleForTesting
+    static final String PACKET_SIZE_FORMAT = "Total packet size for layer %s: %d (bytes)\n";
+
+    @VisibleForTesting
+    static final String PACKET_FAILURE_COUNT_FORMAT =
+            "Total packet failure count for layer %s from %s to %s: %d\n";
+
+    @VisibleForTesting
+    static final String PACKET_FAILURE_SIZE_FORMAT =
+            "Total packet failure size for layer %s from %s to %s: %d (bytes)\n";
+
     private final Context mContext;
     private final VmsClientManager mClientManager;
     private final VmsBrokerService mBrokerService;
     private final Map<String, PublisherProxy> mPublisherProxies = Collections.synchronizedMap(
             new ArrayMap<>());
 
+    @GuardedBy("mPacketCounts")
+    private final Map<VmsLayer, PacketCountAndSize> mPacketCounts = new ArrayMap<>();
+    @GuardedBy("mPacketFailureCounts")
+    private final Map<PacketFailureKey, PacketCountAndSize> mPacketFailureCounts = new ArrayMap<>();
+
+    // PacketCountAndSize keeps track of the cumulative size and number of packets of a specific
+    // VmsLayer that we have seen.
+    private class PacketCountAndSize {
+        long mCount;
+        long mSize;
+    }
+
+    // PacketFailureKey is a triple of the VmsLayer, the publisher and subscriber for which a packet
+    // failed to be sent.
+    private class PacketFailureKey {
+        VmsLayer mVmsLayer;
+        String mPublisher;
+        String mSubscriber;
+
+        PacketFailureKey(VmsLayer vmsLayer, String publisher, String subscriber) {
+            mVmsLayer = vmsLayer;
+            mPublisher = publisher;
+            mSubscriber = subscriber;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof PacketFailureKey)) {
+                return false;
+            }
+
+            PacketFailureKey otherKey = (PacketFailureKey) o;
+            return Objects.equals(mVmsLayer, otherKey.mVmsLayer) && Objects.equals(mPublisher,
+                    otherKey.mPublisher) && Objects.equals(mSubscriber, otherKey.mSubscriber);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mVmsLayer, mPublisher, mSubscriber);
+        }
+    }
+
     public VmsPublisherService(
             Context context,
             VmsBrokerService brokerService,
@@ -77,6 +137,28 @@
     public void dump(PrintWriter writer) {
         writer.println("*" + getClass().getSimpleName() + "*");
         writer.println("mPublisherProxies: " + mPublisherProxies.size());
+        synchronized (mPacketCounts) {
+            for (Map.Entry<VmsLayer, PacketCountAndSize> entry : mPacketCounts.entrySet()) {
+                VmsLayer layer = entry.getKey();
+                PacketCountAndSize countAndSize = entry.getValue();
+                writer.format(PACKET_COUNT_FORMAT, layer, countAndSize.mCount);
+                writer.format(PACKET_SIZE_FORMAT, layer, countAndSize.mSize);
+            }
+        }
+        synchronized (mPacketFailureCounts) {
+            for (Map.Entry<PacketFailureKey, PacketCountAndSize> entry :
+                    mPacketFailureCounts.entrySet()) {
+                PacketFailureKey key = entry.getKey();
+                PacketCountAndSize countAndSize = entry.getValue();
+                VmsLayer layer = key.mVmsLayer;
+                String publisher = key.mPublisher;
+                String subscriber = key.mSubscriber;
+                writer.format(PACKET_FAILURE_COUNT_FORMAT, layer, publisher, subscriber,
+                        countAndSize.mCount);
+                writer.format(PACKET_FAILURE_SIZE_FORMAT, layer, publisher, subscriber,
+                        countAndSize.mSize);
+            }
+        }
     }
 
     @Override
@@ -143,6 +225,26 @@
             mBrokerService.setPublisherLayersOffering(token, offering);
         }
 
+        private void incrementPacketCount(VmsLayer layer, long size) {
+            synchronized (mPacketCounts) {
+                PacketCountAndSize countAndSize = mPacketCounts.computeIfAbsent(layer,
+                        i -> new PacketCountAndSize());
+                countAndSize.mCount++;
+                countAndSize.mSize += size;
+            }
+        }
+
+        private void incrementPacketFailure(VmsLayer layer, String publisher, String subscriber,
+                long size) {
+            synchronized (mPacketFailureCounts) {
+                PacketFailureKey key = new PacketFailureKey(layer, publisher, subscriber);
+                PacketCountAndSize countAndSize = mPacketFailureCounts.computeIfAbsent(key,
+                        i -> new PacketCountAndSize());
+                countAndSize.mCount++;
+                countAndSize.mSize += size;
+            }
+        }
+
         @Override
         public void publish(IBinder token, VmsLayer layer, int publisherId, byte[] payload) {
             assertPermission(token);
@@ -150,16 +252,32 @@
                 Log.d(TAG, String.format("Publishing to %s as %d (%s)", layer, publisherId, mName));
             }
 
+            if (layer == null) {
+                return;
+            }
+
+            int payloadLength = payload != null ? payload.length : 0;
+            incrementPacketCount(layer, payloadLength);
+
             // Send the message to subscribers
             Set<IVmsSubscriberClient> listeners =
                     mBrokerService.getSubscribersForLayerFromPublisher(layer, publisherId);
 
             if (DBG) Log.d(TAG, String.format("Number of subscribers: %d", listeners.size()));
+
+            if (listeners.size() == 0) {
+                // An empty string for the last argument is a special value signalizing zero
+                // subscribers for the VMS_PACKET_FAILURE_REPORTED atom.
+                incrementPacketFailure(layer, mName, "", payloadLength);
+            }
+
             for (IVmsSubscriberClient listener : listeners) {
                 try {
                     listener.onVmsMessageReceived(layer, payload);
                 } catch (RemoteException ex) {
-                    Log.e(TAG, String.format("Unable to publish to listener: %s", listener));
+                    String subscriberName = mBrokerService.getPackageName(listener);
+                    incrementPacketFailure(layer, mName, subscriberName, payloadLength);
+                    Log.e(TAG, String.format("Unable to publish to listener: %s", subscriberName));
                 }
             }
         }
diff --git a/service/src/com/android/car/vms/VmsBrokerService.java b/service/src/com/android/car/vms/VmsBrokerService.java
index 216c8fd..26626ff 100644
--- a/service/src/com/android/car/vms/VmsBrokerService.java
+++ b/service/src/com/android/car/vms/VmsBrokerService.java
@@ -22,19 +22,24 @@
 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
@@ -44,15 +49,26 @@
     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();
@@ -95,8 +111,17 @@
     /**
      * Constructs new broker service.
      */
-    public VmsBrokerService() {
+    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;
     }
 
     /**
@@ -143,6 +168,8 @@
     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());
         }
     }
 
@@ -172,6 +199,9 @@
 
             // 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();
@@ -219,6 +249,9 @@
 
             // 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();
@@ -263,6 +296,9 @@
         boolean subscriptionStateChanged;
         synchronized (mLock) {
             subscriptionStateChanged = mRouting.removeDeadSubscriber(subscriber);
+
+            // Remove mapping from binder to package name of subscriber.
+            mBinderPackage.remove(subscriber.asBinder());
         }
         if (subscriptionStateChanged) {
             notifyOfSubscriptionChange();
@@ -358,6 +394,15 @@
         }
     }
 
+    /**
+     * 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) {
@@ -388,4 +433,21 @@
             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;
+        }
+    }
 }
diff --git a/tests/carservice_test/src/com/android/car/vms/VmsBrokerServiceTest.java b/tests/carservice_test/src/com/android/car/vms/VmsBrokerServiceTest.java
new file mode 100644
index 0000000..5ce7df5
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/vms/VmsBrokerServiceTest.java
@@ -0,0 +1,197 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.car.vms.IVmsSubscriberClient;
+import android.car.vms.VmsLayer;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Process;
+
+import androidx.test.filters.MediumTest;
+
+import org.junit.Test;
+
+import java.util.function.IntSupplier;
+
+@MediumTest
+public class VmsBrokerServiceTest {
+
+    class MockIntProvider implements IntSupplier {
+        private int[] mInts;
+        private int mIdx;
+
+        MockIntProvider(int... ints) {
+            mInts = ints;
+            mIdx = 0;
+        }
+
+        public int getAsInt() {
+            int ret = mInts[mIdx];
+            mIdx++;
+            return ret;
+        }
+    }
+
+    /**
+     * Test that adding a subscriber to VmsBrokerService also keeps track of the package name for
+     * a given subscriber. Also checks that if we remove a dead subscriber, we no longer track the
+     * package name associated with it.
+     */
+    @Test
+    public void testAddSubscription() {
+        PackageManager packageManager = mock(PackageManager.class);
+        IVmsSubscriberClient subscriberClient = mock(IVmsSubscriberClient.class);
+        Binder binder = mock(Binder.class);
+        VmsLayer layer = mock(VmsLayer.class);
+        when(packageManager.getNameForUid(0)).thenReturn("test.package1");
+        when(subscriberClient.asBinder()).thenReturn(binder);
+
+        VmsBrokerService broker = new VmsBrokerService(packageManager, () -> 200, () -> 0);
+        broker.addSubscription(subscriberClient);
+        assertThat(broker.getPackageName(subscriberClient)).isEqualTo("test.package1");
+        broker.removeDeadSubscriber(subscriberClient);
+        assertThat(broker.getPackageName(subscriberClient)).isNull();
+    }
+
+    @Test
+    public void testAddSubscriptionLayer() {
+        PackageManager packageManager = mock(PackageManager.class);
+        IVmsSubscriberClient subscriberClient = mock(IVmsSubscriberClient.class);
+        Binder binder = mock(Binder.class);
+        VmsLayer layer = mock(VmsLayer.class);
+        when(packageManager.getNameForUid(0)).thenReturn("test.package2");
+        when(subscriberClient.asBinder()).thenReturn(binder);
+
+        VmsBrokerService broker = new VmsBrokerService(packageManager, () -> 200, () -> 0);
+        broker.addSubscription(subscriberClient, layer);
+        assertThat(broker.getPackageName(subscriberClient)).isEqualTo("test.package2");
+        broker.removeDeadSubscriber(subscriberClient);
+        assertThat(broker.getPackageName(subscriberClient)).isNull();
+    }
+
+    @Test
+    public void testAddSubscriptionLayerVersion() {
+        PackageManager packageManager = mock(PackageManager.class);
+        IVmsSubscriberClient subscriberClient = mock(IVmsSubscriberClient.class);
+        Binder binder = mock(Binder.class);
+        VmsLayer layer = mock(VmsLayer.class);
+        when(packageManager.getNameForUid(0)).thenReturn("test.package3");
+        when(subscriberClient.asBinder()).thenReturn(binder);
+
+        VmsBrokerService broker = new VmsBrokerService(packageManager, () -> 200, () -> 0);
+        broker.addSubscription(subscriberClient, layer, 1234);
+        assertThat(broker.getPackageName(subscriberClient)).isEqualTo("test.package3");
+        broker.removeDeadSubscriber(subscriberClient);
+        assertThat(broker.getPackageName(subscriberClient)).isNull();
+    }
+
+    @Test
+    public void testMultipleSubscriptionsSameClientCallsPackageManagerOnce() {
+        PackageManager packageManager = mock(PackageManager.class);
+        IVmsSubscriberClient subscriberClient = mock(IVmsSubscriberClient.class);
+        Binder binder = mock(Binder.class);
+        when(subscriberClient.asBinder()).thenReturn(binder);
+        when(packageManager.getNameForUid(0)).thenReturn("test.package3");
+
+        VmsBrokerService broker = new VmsBrokerService(packageManager, () -> 0, () -> 0);
+        broker.addSubscription(subscriberClient);
+        broker.addSubscription(subscriberClient);
+        // The second argument isn't necessary but is here for clarity.
+        verify(packageManager, times(1)).getNameForUid(0);
+    }
+
+    @Test
+    public void testUnknownPackageName() {
+        PackageManager packageManager = mock(PackageManager.class);
+        IVmsSubscriberClient subscriberClient = mock(IVmsSubscriberClient.class);
+        Binder binder = mock(Binder.class);
+        when(subscriberClient.asBinder()).thenReturn(binder);
+        when(packageManager.getNameForUid(0)).thenReturn(null);
+
+        VmsBrokerService broker = new VmsBrokerService(packageManager, () -> 0, () -> 0);
+        broker.addSubscription(subscriberClient);
+        assertThat(broker.getPackageName(subscriberClient)).isEqualTo(
+                VmsBrokerService.UNKNOWN_PACKAGE);
+    }
+
+    /**
+     * Tests that if the HAL is a subscriber, we record its package name as HalClient.
+     */
+    @Test
+    public void testAddingHalSubscriberSavesPackageName() {
+        PackageManager packageManager = mock(PackageManager.class);
+        IVmsSubscriberClient subscriberClient = mock(IVmsSubscriberClient.class);
+
+        VmsBrokerService broker = new VmsBrokerService(packageManager, () -> Process.myPid(),
+                () -> Process.SYSTEM_UID);
+        broker.addSubscription(subscriberClient);
+        assertThat(broker.getPackageName(subscriberClient)).isEqualTo(VmsBrokerService.HAL_CLIENT);
+    }
+
+    @Test
+    public void testMultipleSubscriptionsPackageManager() {
+        PackageManager packageManager = mock(PackageManager.class);
+
+        IVmsSubscriberClient subscriberClient1 = mock(IVmsSubscriberClient.class);
+        Binder binder1 = mock(Binder.class);
+        when(subscriberClient1.asBinder()).thenReturn(binder1);
+
+        IVmsSubscriberClient subscriberClient2 = mock(IVmsSubscriberClient.class);
+        Binder binder2 = mock(Binder.class);
+        when(subscriberClient2.asBinder()).thenReturn(binder2);
+
+        IVmsSubscriberClient subscriberClient3 = mock(IVmsSubscriberClient.class);
+        Binder binder3 = mock(Binder.class);
+        when(subscriberClient3.asBinder()).thenReturn(binder3);
+
+        // Tests a client with a different UID but the same package as subscriberClient1
+        IVmsSubscriberClient subscriberClient4 = mock(IVmsSubscriberClient.class);
+        Binder binder4 = mock(Binder.class);
+        when(subscriberClient4.asBinder()).thenReturn(binder4);
+
+        when(packageManager.getNameForUid(0)).thenReturn("test.package0");
+        when(packageManager.getNameForUid(1)).thenReturn("test.package1");
+        when(packageManager.getNameForUid(2)).thenReturn("test.package2");
+        when(packageManager.getNameForUid(3)).thenReturn("test.package0");
+
+        VmsBrokerService broker = new VmsBrokerService(packageManager, () -> 10,
+                new MockIntProvider(0, 1, 2, 3));
+
+        broker.addSubscription(subscriberClient1);
+        broker.addSubscription(subscriberClient2);
+        broker.addSubscription(subscriberClient3);
+        broker.addSubscription(subscriberClient4);
+
+        verify(packageManager).getNameForUid(0);
+        verify(packageManager).getNameForUid(1);
+        verify(packageManager).getNameForUid(2);
+        verify(packageManager).getNameForUid(3);
+
+        assertThat(broker.getPackageName(subscriberClient1)).isEqualTo("test.package0");
+        assertThat(broker.getPackageName(subscriberClient2)).isEqualTo("test.package1");
+        assertThat(broker.getPackageName(subscriberClient3)).isEqualTo("test.package2");
+        assertThat(broker.getPackageName(subscriberClient4)).isEqualTo("test.package0");
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/VmsPublisherServiceTest.java b/tests/carservice_unit_test/src/com/android/car/VmsPublisherServiceTest.java
index 6e0c2fe..2c63ef5 100644
--- a/tests/carservice_unit_test/src/com/android/car/VmsPublisherServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/VmsPublisherServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.car;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
@@ -52,12 +54,16 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 
 @SmallTest
 public class VmsPublisherServiceTest {
@@ -66,8 +72,14 @@
     private static final VmsLayersOffering OFFERING = new VmsLayersOffering(Collections.emptySet(),
             54321);
     private static final VmsLayer LAYER = new VmsLayer(1, 2, 3);
+    private static final VmsLayer LAYER2 = new VmsLayer(2, 2, 8);
+    private static final VmsLayer LAYER3 = new VmsLayer(3, 2, 8);
+    private static final VmsLayer LAYER4 = new VmsLayer(4, 2, 8);
+
     private static final int PUBLISHER_ID = 54321;
     private static final byte[] PAYLOAD = new byte[]{1, 2, 3, 4};
+    private static final byte[] PAYLOAD2 = new byte[]{1, 2, 3, 4, 5, 6};
+    private static final byte[] PAYLOAD3 = new byte[]{10, 12, 93, 4, 5, 6, 1, 1, 1};
 
     @Rule
     public MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -84,6 +96,10 @@
     private IVmsSubscriberClient mSubscriberClient;
     @Mock
     private IVmsSubscriberClient mSubscriberClient2;
+    @Mock
+    private IVmsSubscriberClient mThrowingSubscriberClient;
+    @Mock
+    private IVmsSubscriberClient mThrowingSubscriberClient2;
 
     private VmsPublisherService mPublisherService;
     private MockPublisherClient mPublisherClient;
@@ -96,6 +112,7 @@
         mPublisherClient2 = new MockPublisherClient();
         when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER, PUBLISHER_ID))
                 .thenReturn(new HashSet<>(Arrays.asList(mSubscriberClient, mSubscriberClient2)));
+
     }
 
     @Test
@@ -175,6 +192,15 @@
     }
 
     @Test
+    public void testPublishNullLayerAndNullPayload() throws Exception {
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+
+        // We just want to ensure that no exceptions are thrown here.
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, null, PUBLISHER_ID,
+                null);
+    }
+
+    @Test
     public void testPublish_ClientError() throws Exception {
         mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
         doThrow(new RemoteException()).when(mSubscriberClient).onVmsMessageReceived(LAYER, PAYLOAD);
@@ -274,6 +300,341 @@
     }
 
     @Test
+    public void testDump_getPacketCount() throws Exception {
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        PrintWriter printWriter = new PrintWriter(outputStream);
+
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
+                PAYLOAD);
+        mPublisherService.dump(printWriter);
+
+        printWriter.flush();
+        String dumpString = outputStream.toString();
+        String expectedPacketCountString = String.format(VmsPublisherService.PACKET_COUNT_FORMAT,
+                LAYER, 1L);
+        String expectedPacketSizeString = String.format(VmsPublisherService.PACKET_SIZE_FORMAT,
+                LAYER, PAYLOAD.length);
+        assertThat(dumpString.contains(expectedPacketCountString)).isTrue();
+        assertThat(dumpString.contains(expectedPacketSizeString)).isTrue();
+    }
+
+    @Test
+    public void testDump_getPacketCounts() throws Exception {
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        PrintWriter printWriter = new PrintWriter(outputStream);
+
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
+                PAYLOAD);
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER2, PUBLISHER_ID,
+                PAYLOAD);
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
+                PAYLOAD2);
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
+                PAYLOAD);
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER3, PUBLISHER_ID,
+                PAYLOAD);
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
+                PAYLOAD3);
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
+                PAYLOAD3);
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER2, PUBLISHER_ID,
+                PAYLOAD3);
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
+                PAYLOAD3);
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER3, PUBLISHER_ID,
+                PAYLOAD);
+        mPublisherService.dump(printWriter);
+
+        printWriter.flush();
+        String dumpString = outputStream.toString();
+
+        // LAYER called 6 times with PAYLOAD 2 times, PAYLOAD2 1 time, PAYLOAD3 3 times
+        String expectedPacketCountString1 = String.format(VmsPublisherService.PACKET_COUNT_FORMAT,
+                LAYER, 6L);
+        String expectedPacketSizeString1 = String.format(VmsPublisherService.PACKET_SIZE_FORMAT,
+                LAYER, 2 * PAYLOAD.length + PAYLOAD2.length + 3 * PAYLOAD3.length);
+
+        // LAYER2 called 2 times with PAYLOAD 1 time, PAYLOAD2 0 time, PAYLOAD3 1 times
+        String expectedPacketCountString2 = String.format(VmsPublisherService.PACKET_COUNT_FORMAT,
+                LAYER2, 2L);
+        String expectedPacketSizeString2 = String.format(VmsPublisherService.PACKET_SIZE_FORMAT,
+                LAYER2, PAYLOAD.length + PAYLOAD3.length);
+
+        // LAYER3 called 2 times with PAYLOAD 2 times, PAYLOAD2 0 time, PAYLOAD3 0 times
+        String expectedPacketCountString3 = String.format(VmsPublisherService.PACKET_COUNT_FORMAT,
+                LAYER3, 2L);
+        String expectedPacketSizeString3 = String.format(VmsPublisherService.PACKET_SIZE_FORMAT,
+                LAYER3, 2 * PAYLOAD.length);
+
+        assertThat(dumpString.contains(expectedPacketCountString1)).isTrue();
+        assertThat(dumpString.contains(expectedPacketSizeString1)).isTrue();
+        assertThat(dumpString.contains(expectedPacketCountString2)).isTrue();
+        assertThat(dumpString.contains(expectedPacketSizeString2)).isTrue();
+        assertThat(dumpString.contains(expectedPacketCountString3)).isTrue();
+        assertThat(dumpString.contains(expectedPacketSizeString3)).isTrue();
+    }
+
+    @Test
+    public void testDumpNoListeners_getPacketFailureCount() throws Exception {
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        PrintWriter printWriter = new PrintWriter(outputStream);
+
+        // Layer 2 has no listeners and should therefore result in a packet failure to be recorded.
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER2, PUBLISHER_ID,
+                PAYLOAD);
+        mPublisherService.dump(printWriter);
+
+        printWriter.flush();
+        String dumpString = outputStream.toString();
+
+        String expectedPacketFailureString = String.format(
+                VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT,
+                LAYER2, "SomeClient", "", 1L);
+        String expectedPacketFailureSizeString = String.format(
+                VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT,
+                LAYER2, "SomeClient", "", PAYLOAD.length);
+
+        assertThat(dumpString.contains(expectedPacketFailureString)).isTrue();
+        assertThat(dumpString.contains(expectedPacketFailureSizeString)).isTrue();
+    }
+
+    @Test
+    public void testDumpNoListeners_getPacketFailureCounts() throws Exception {
+        // LAYER2 and LAYER3 both have no listeners
+        when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER2, PUBLISHER_ID))
+                .thenReturn(new HashSet<>());
+        when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER3, PUBLISHER_ID))
+                .thenReturn(new HashSet<>());
+
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient2", mPublisherClient2.asBinder());
+
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        PrintWriter printWriter = new PrintWriter(outputStream);
+
+        // Layer 2 has no listeners and should therefore result in a packet failure to be recorded.
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER2, PUBLISHER_ID,
+                PAYLOAD);
+        mPublisherClient2.mPublisherService.publish(mPublisherClient2.mToken, LAYER3, PUBLISHER_ID,
+                PAYLOAD);
+
+        mPublisherService.dump(printWriter);
+
+        printWriter.flush();
+        String dumpString = outputStream.toString();
+
+        String expectedPacketFailureString = String.format(
+                VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT,
+                LAYER2, "SomeClient", "", 1L);
+        String expectedPacketFailureString2 = String.format(
+                VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT,
+                LAYER3, "SomeClient2", "", 1L);
+        String expectedPacketFailureSizeString = String.format(
+                VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT,
+                LAYER2, "SomeClient", "", PAYLOAD.length);
+        String expectedPacketFailureSizeString2 = String.format(
+                VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT,
+                LAYER3, "SomeClient2", "", PAYLOAD.length);
+
+        assertThat(dumpString.contains(expectedPacketFailureString)).isTrue();
+        assertThat(dumpString.contains(expectedPacketFailureSizeString)).isTrue();
+        assertThat(dumpString.contains(expectedPacketFailureString2)).isTrue();
+        assertThat(dumpString.contains(expectedPacketFailureSizeString2)).isTrue();
+    }
+
+    @Test
+    public void testDumpRemoteException_getPacketFailureCount() throws Exception {
+        // The listener on LAYER3 will throw on LAYER3 and PAYLOAD
+        Mockito.doThrow(new RemoteException()).when(mThrowingSubscriberClient).onVmsMessageReceived(
+                LAYER3, PAYLOAD);
+        when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER3, PUBLISHER_ID))
+                .thenReturn(new HashSet<>(Arrays.asList(mThrowingSubscriberClient)));
+        when(mBrokerService.getPackageName(mThrowingSubscriberClient)).thenReturn("Thrower");
+
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        PrintWriter printWriter = new PrintWriter(outputStream);
+
+        // Layer 2 has no listeners and should therefore result in a packet failure to be recorded.
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER3, PUBLISHER_ID,
+                PAYLOAD);
+
+        mPublisherService.dump(printWriter);
+
+        printWriter.flush();
+        String dumpString = outputStream.toString();
+
+        String expectedPacketFailureString = String.format(
+                VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT,
+                LAYER3, "SomeClient", "Thrower", 1L);
+        String expectedPacketFailureSizeString = String.format(
+                VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT,
+                LAYER3, "SomeClient", "Thrower", PAYLOAD.length);
+
+        assertThat(dumpString.contains(expectedPacketFailureString)).isTrue();
+        assertThat(dumpString.contains(expectedPacketFailureSizeString)).isTrue();
+    }
+
+    @Test
+    public void testDumpRemoteException_getPacketFailureCounts() throws Exception {
+        // The listeners will throw on LAYER3 or LAYER4 and PAYLOAD
+        Mockito.doThrow(new RemoteException()).when(mThrowingSubscriberClient).onVmsMessageReceived(
+                LAYER3, PAYLOAD);
+        Mockito.doThrow(new RemoteException()).when(mThrowingSubscriberClient).onVmsMessageReceived(
+                LAYER4, PAYLOAD);
+        Mockito.doThrow(new RemoteException()).when(
+                mThrowingSubscriberClient2).onVmsMessageReceived(LAYER3, PAYLOAD);
+        Mockito.doThrow(new RemoteException()).when(
+                mThrowingSubscriberClient2).onVmsMessageReceived(LAYER4, PAYLOAD);
+
+        when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER3, PUBLISHER_ID))
+                .thenReturn(new HashSet<>(
+                        Arrays.asList(mThrowingSubscriberClient, mThrowingSubscriberClient2)));
+        when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER4, PUBLISHER_ID))
+                .thenReturn(new HashSet<>(
+                        Arrays.asList(mThrowingSubscriberClient, mThrowingSubscriberClient2)));
+
+        when(mBrokerService.getPackageName(mThrowingSubscriberClient)).thenReturn("Thrower");
+        when(mBrokerService.getPackageName(mThrowingSubscriberClient2)).thenReturn("Thrower2");
+
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherService.onClientConnected("SomeClient2", mPublisherClient2.asBinder());
+
+        // Layer 2 has no listeners and should therefore result in a packet failure to be recorded.
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER3, PUBLISHER_ID,
+                PAYLOAD);
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER3, PUBLISHER_ID,
+                PAYLOAD);
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER4, PUBLISHER_ID,
+                PAYLOAD);
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER4, PUBLISHER_ID,
+                PAYLOAD);
+
+        mPublisherClient2.mPublisherService.publish(mPublisherClient2.mToken, LAYER3, PUBLISHER_ID,
+                PAYLOAD);
+        mPublisherClient2.mPublisherService.publish(mPublisherClient2.mToken, LAYER3, PUBLISHER_ID,
+                PAYLOAD);
+        mPublisherClient2.mPublisherService.publish(mPublisherClient2.mToken, LAYER4, PUBLISHER_ID,
+                PAYLOAD);
+        mPublisherClient2.mPublisherService.publish(mPublisherClient2.mToken, LAYER4, PUBLISHER_ID,
+                PAYLOAD);
+
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        PrintWriter printWriter = new PrintWriter(outputStream);
+        mPublisherService.dump(printWriter);
+
+        printWriter.flush();
+        String dumpString = outputStream.toString();
+
+        List<String> expectedStrings = Arrays.asList(
+                String.format(VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT, LAYER3, "SomeClient",
+                        "Thrower", 2L),
+                String.format(VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT, LAYER3, "SomeClient",
+                        "Thrower2", 2L),
+                String.format(VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT, LAYER4, "SomeClient",
+                        "Thrower", 2L),
+                String.format(VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT, LAYER4, "SomeClient",
+                        "Thrower2", 2L),
+                String.format(VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT, LAYER3,
+                        "SomeClient2",
+                        "Thrower", 2L),
+                String.format(VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT, LAYER3,
+                        "SomeClient2",
+                        "Thrower2", 2L),
+                String.format(VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT, LAYER4,
+                        "SomeClient2",
+                        "Thrower", 2L),
+                String.format(VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT, LAYER4,
+                        "SomeClient2",
+                        "Thrower2", 2L),
+
+                String.format(VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT, LAYER3, "SomeClient",
+                        "Thrower", 2 * PAYLOAD.length),
+                String.format(VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT, LAYER3, "SomeClient",
+                        "Thrower2", 2 * PAYLOAD.length),
+                String.format(VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT, LAYER4, "SomeClient",
+                        "Thrower", 2 * PAYLOAD.length),
+                String.format(VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT, LAYER4, "SomeClient",
+                        "Thrower2", 2 * PAYLOAD.length),
+                String.format(VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT, LAYER3, "SomeClient2",
+                        "Thrower", 2 * PAYLOAD.length),
+                String.format(VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT, LAYER3, "SomeClient2",
+                        "Thrower2", 2 * PAYLOAD.length),
+                String.format(VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT, LAYER4, "SomeClient2",
+                        "Thrower", 2 * PAYLOAD.length),
+                String.format(VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT, LAYER4, "SomeClient2",
+                        "Thrower2", 2 * PAYLOAD.length));
+
+        for (String expected : expectedStrings) {
+            assertThat(dumpString.contains(expected)).isTrue();
+        }
+    }
+
+    @Test
+    public void testDump_getAllMetrics() throws Exception {
+
+        // LAYER3 has no subscribers
+        when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER3, PUBLISHER_ID))
+                .thenReturn(new HashSet<>(Arrays.asList()));
+
+        // LAYER4 has a subscriber that will always throw
+        Mockito.doThrow(new RemoteException()).when(mThrowingSubscriberClient).onVmsMessageReceived(
+                LAYER4, PAYLOAD);
+
+        when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER4, PUBLISHER_ID))
+                .thenReturn(new HashSet<>(
+                        Arrays.asList(mThrowingSubscriberClient)));
+
+        when(mBrokerService.getPackageName(mThrowingSubscriberClient)).thenReturn("Thrower");
+
+        mPublisherService.onClientConnected("SomeClient", mPublisherClient.asBinder());
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
+                PAYLOAD);
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
+                PAYLOAD2);
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER3, PUBLISHER_ID,
+                PAYLOAD3);
+        mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER4, PUBLISHER_ID,
+                PAYLOAD);
+
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        PrintWriter printWriter = new PrintWriter(outputStream);
+        mPublisherService.dump(printWriter);
+
+        printWriter.flush();
+        String dumpString = outputStream.toString();
+
+        List<String> expectedStrings = Arrays.asList(
+                String.format(VmsPublisherService.PACKET_COUNT_FORMAT, LAYER, 2),
+                String.format(VmsPublisherService.PACKET_COUNT_FORMAT, LAYER3, 1),
+                String.format(VmsPublisherService.PACKET_COUNT_FORMAT, LAYER4, 1),
+                String.format(VmsPublisherService.PACKET_SIZE_FORMAT, LAYER,
+                        PAYLOAD.length + PAYLOAD2.length),
+                String.format(VmsPublisherService.PACKET_SIZE_FORMAT, LAYER3, PAYLOAD3.length),
+                String.format(VmsPublisherService.PACKET_SIZE_FORMAT, LAYER4, PAYLOAD.length),
+                String.format(VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT, LAYER3, "SomeClient",
+                        "",
+                        1),
+                String.format(VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT, LAYER3, "SomeClient",
+                        "",
+                        PAYLOAD3.length),
+                String.format(VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT, LAYER4, "SomeClient",
+                        "Thrower", 1),
+                String.format(VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT, LAYER4, "SomeClient",
+                        "Thrower", PAYLOAD.length)
+        );
+
+        for (String expected : expectedStrings) {
+            assertThat(dumpString.contains(expected)).isTrue();
+        }
+    }
+
+
+    @Test
     public void testRelease() {
         mPublisherService.release();
         verify(mClientManager).unregisterConnectionListener(mPublisherService);