Merge "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:" into qt-dev
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 502ebbb..31627b9 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);