Minor improvements on ApiCallStats and ApiCallStatsTest.

ApiCallStats:
 - Make it final
 - Hide constructor
 - Encapsulated fields
 - Removed redundant @NonNull annotations
 - Throws (ISE instead of IAE) when both app and sdk name are set

ApiCallStatsTest:
 - Extends AdServiceUnitTestCase
 - Uses Truth instead of JUnit assertions
 - Added tests for equals() / hashcode() / toString()
 - Split testIncompleteInputBuilder_throwsIAE in 2 tests
 - Uses non-zero result code

Test: atest AdServicesServiceCoreUnitTests:ApiCallStatsTest
Bug: 270974848

Change-Id: I6d623cd549ccde12ba06d9bbfdd7b7fe4caf6666
diff --git a/adservices/service-core/java/com/android/adservices/service/stats/ApiCallStats.java b/adservices/service-core/java/com/android/adservices/service/stats/ApiCallStats.java
index 018cf37..34a6598 100644
--- a/adservices/service-core/java/com/android/adservices/service/stats/ApiCallStats.java
+++ b/adservices/service-core/java/com/android/adservices/service/stats/ApiCallStats.java
@@ -16,21 +16,19 @@
 
 package com.android.adservices.service.stats;
 
-import android.annotation.NonNull;
-
 import java.util.Objects;
 
 /** Class for Api Call Stats. */
-public class ApiCallStats {
+public final class ApiCallStats {
     private int mCode;
     private int mApiClass;
-    int mApiName;
-    String mAppPackageName;
-    String mSdkPackageName;
-    int mLatencyMillisecond;
-    int mResultCode;
+    private int mApiName;
+    private String mAppPackageName;
+    private String mSdkPackageName;
+    private int mLatencyMillisecond;
+    private int mResultCode;
 
-    public ApiCallStats() {}
+    private ApiCallStats() {}
 
     @Override
     public boolean equals(Object obj) {
@@ -59,37 +57,30 @@
                 mResultCode);
     }
 
-    @NonNull
     public int getCode() {
         return mCode;
     }
 
-    @NonNull
     public int getApiClass() {
         return mApiClass;
     }
 
-    @NonNull
     public int getApiName() {
         return mApiName;
     }
 
-    @NonNull
     public String getAppPackageName() {
         return mAppPackageName;
     }
 
-    @NonNull
     public String getSdkPackageName() {
         return mSdkPackageName;
     }
 
-    @NonNull
     public int getLatencyMillisecond() {
         return mLatencyMillisecond;
     }
 
-    @NonNull
     public int getResultCode() {
         return mResultCode;
     }
@@ -118,60 +109,60 @@
 
     /** Builder for {@link ApiCallStats}. */
     public static final class Builder {
-        private final ApiCallStats mBuilding;
+        private final ApiCallStats mBuilding = new ApiCallStats();
 
         public Builder() {
-            mBuilding = new ApiCallStats();
         }
 
         /** See {@link ApiCallStats#getCode()} . */
-        public @NonNull ApiCallStats.Builder setCode(int code) {
+        public Builder setCode(int code) {
             mBuilding.mCode = code;
             return this;
         }
 
         /** See {@link ApiCallStats#getApiClass()} . */
-        public @NonNull ApiCallStats.Builder setApiClass(int apiClass) {
+        public Builder setApiClass(int apiClass) {
             mBuilding.mApiClass = apiClass;
             return this;
         }
 
         /** See {@link ApiCallStats#getApiName()} . */
-        public @NonNull ApiCallStats.Builder setApiName(int apiName) {
+        public Builder setApiName(int apiName) {
             mBuilding.mApiName = apiName;
             return this;
         }
 
         /** See {@link ApiCallStats#getAppPackageName()} . */
-        public @NonNull ApiCallStats.Builder setAppPackageName(@NonNull String appPackageName) {
-            Objects.requireNonNull(appPackageName);
-            mBuilding.mAppPackageName = appPackageName;
+        public Builder setAppPackageName(String appPackageName) {
+            mBuilding.mAppPackageName = Objects.requireNonNull(appPackageName);
             return this;
         }
 
         /** See {@link ApiCallStats#getSdkPackageName()}. */
-        public @NonNull ApiCallStats.Builder setSdkPackageName(@NonNull String sdkPackageName) {
-            Objects.requireNonNull(sdkPackageName);
-            mBuilding.mSdkPackageName = sdkPackageName;
+        public Builder setSdkPackageName(String sdkPackageName) {
+            mBuilding.mSdkPackageName = Objects.requireNonNull(sdkPackageName);
             return this;
         }
 
         /** See {@link ApiCallStats#getLatencyMillisecond()}. */
-        public @NonNull ApiCallStats.Builder setLatencyMillisecond(int latencyMillisecond) {
+        public Builder setLatencyMillisecond(int latencyMillisecond) {
             mBuilding.mLatencyMillisecond = latencyMillisecond;
             return this;
         }
 
         /** See {@link ApiCallStats#getResultCode()}. */
-        public @NonNull ApiCallStats.Builder setResultCode(int resultCode) {
+        public Builder setResultCode(int resultCode) {
             mBuilding.mResultCode = resultCode;
             return this;
         }
 
         /** Build the {@link ApiCallStats}. */
-        public @NonNull ApiCallStats build() {
-            if (mBuilding.mAppPackageName == null || mBuilding.mSdkPackageName == null) {
-                throw new IllegalArgumentException("appPackageName or sdkPackageName is null");
+        public ApiCallStats build() {
+            if (mBuilding.mAppPackageName == null) {
+                throw new IllegalStateException("must call setAppPackageName()");
+            }
+            if (mBuilding.mSdkPackageName == null) {
+                throw new IllegalStateException("must call setSdkPackageName()");
             }
             return mBuilding;
         }
diff --git a/adservices/tests/test-util/java/com/android/adservices/common/AdServicesTestCase.java b/adservices/tests/test-util/java/com/android/adservices/common/AdServicesTestCase.java
index 6ec9ab3..090fea8 100644
--- a/adservices/tests/test-util/java/com/android/adservices/common/AdServicesTestCase.java
+++ b/adservices/tests/test-util/java/com/android/adservices/common/AdServicesTestCase.java
@@ -20,6 +20,7 @@
 import android.os.SystemProperties;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
 
 import com.google.common.truth.Expect;
@@ -30,6 +31,8 @@
 import org.junit.Before;
 import org.junit.Rule;
 
+import java.util.Objects;
+
 // TODO(b/285014040): need to add unit tests for this class itself, as it's now providing logic.
 
 // Superclass for all other "base classes"
@@ -139,6 +142,43 @@
                 .start();
     }
 
+    // TODO(b/285014040): refactor to take Object... instead (once it's unit tested)
+    /**
+     * Helper method that uses {@code expect} to assert the class properly implement {@code
+     * equals()} and {@code hashCode()}.
+     *
+     * @param obj1 object that is equals to {@code obj2}
+     * @param obj2 object that is equals to {@code obj1}
+     */
+    protected final void expectObjectsAreEqual(Object obj1, Object obj2) {
+        Objects.requireNonNull(obj1, "1st arg cannot be null");
+        Objects.requireNonNull(obj2, "2nd arg cannot be null");
+
+        expect.withMessage("1st obj (%s)", obj1).that(obj1).isEqualTo(obj2);
+        expect.withMessage("2nd obj (%s)", obj2).that(obj2).isEqualTo(obj1);
+        expect.withMessage("hashCode of %s", obj1).that(obj1.hashCode()).isEqualTo(obj2.hashCode());
+    }
+
+    // TODO(b/285014040): refactor to take Object... instead (once it's unit tested)
+    /**
+     * Helper method that uses {@code expect} to assert the class properly implement {@code
+     * equals()} and {@code hashCode()}.
+     *
+     * @param obj1 object that is not equal to {@code obj2}
+     * @param obj2 object that is not equal to {@code obj1}
+     */
+    protected final void expectObjectsAreNotEqual(Object obj1, @Nullable Object obj2) {
+        Objects.requireNonNull(obj1, "1st arg cannot be null");
+
+        expect.withMessage("1st obj (%s)", obj1).that(obj1).isNotEqualTo(obj2);
+        expect.withMessage("2nd obj (%s)", obj2).that(obj2).isNotEqualTo(obj1);
+        if (obj2 != null) {
+            expect.withMessage("hashCode of %s", obj1)
+                    .that(obj1.hashCode())
+                    .isNotEqualTo(obj2.hashCode());
+        }
+    }
+
     private void sleepAfterTest() {
         int napTimeMs = SystemProperties.getInt(PROP_DELAY_AFTER_TEST, 0);
         if (napTimeMs <= 0) {
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/ApiCallStatsTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/ApiCallStatsTest.java
index 82cfc4d..e2f2a4a 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/ApiCallStatsTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/stats/ApiCallStatsTest.java
@@ -16,74 +16,235 @@
 
 package com.android.adservices.service.stats;
 
-import static com.android.adservices.ResultCode.RESULT_OK;
+import static android.adservices.common.AdServicesStatusUtils.STATUS_INVALID_ARGUMENT;
+
 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED;
 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_CLASS__TARGETING;
 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS;
 
-import org.junit.Assert;
+import static org.junit.Assert.assertThrows;
+
+import com.android.adservices.common.AdServicesUnitTestCase;
+
 import org.junit.Test;
 
-/**
- * Unit test for {@link ApiCallStats}.
- */
-public class ApiCallStatsTest {
+/** Unit test for {@link ApiCallStats}. */
+public final class ApiCallStatsTest extends AdServicesUnitTestCase {
 
-    private final String mPackageName = "com.android.test";
-    private final String mSdkName = "com.android.container";
-    private static final int LATENCY = 100;
+    private static final int LATENCY_MS = 100;
+
+    private final String mAppPackageName = "com.android.test";
+    private final String mSdkPackageName = "com.android.container";
+
     @Test
     public void testBuilderCreateSuccess() {
-        ApiCallStats stats = new ApiCallStats.Builder()
-                .setCode(AD_SERVICES_API_CALLED)
-                .setApiClass(AD_SERVICES_API_CALLED__API_CLASS__TARGETING)
-                .setApiName(AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS)
-                .setAppPackageName(mPackageName)
-                .setSdkPackageName(mSdkName)
-                .setLatencyMillisecond(LATENCY)
-                .setResultCode(RESULT_OK)
-                .build();
-        Assert.assertEquals(AD_SERVICES_API_CALLED, stats.getCode());
-        Assert.assertEquals(AD_SERVICES_API_CALLED__API_CLASS__TARGETING, stats.getApiClass());
-        Assert.assertEquals(AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS, stats.getApiName());
-        Assert.assertEquals(mPackageName, stats.getAppPackageName());
-        Assert.assertEquals(mSdkName, stats.getSdkPackageName());
-        Assert.assertEquals(LATENCY, stats.getLatencyMillisecond());
-        Assert.assertEquals(RESULT_OK, stats.getResultCode());
+        ApiCallStats stats =
+                new ApiCallStats.Builder()
+                        .setCode(AD_SERVICES_API_CALLED)
+                        .setApiClass(AD_SERVICES_API_CALLED__API_CLASS__TARGETING)
+                        .setApiName(AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS)
+                        .setAppPackageName(mAppPackageName)
+                        .setSdkPackageName(mSdkPackageName)
+                        .setLatencyMillisecond(LATENCY_MS)
+                        .setResultCode(STATUS_INVALID_ARGUMENT)
+                        .build();
+
+        expect.withMessage("%s.getCode()", stats)
+                .that(stats.getCode())
+                .isEqualTo(AD_SERVICES_API_CALLED);
+        expect.withMessage("%s.getApiClass()", stats)
+                .that(stats.getApiClass())
+                .isEqualTo(AD_SERVICES_API_CALLED__API_CLASS__TARGETING);
+        expect.withMessage("%s.getApiName()", stats)
+                .that(stats.getApiName())
+                .isEqualTo(AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS);
+        expect.withMessage("%s.getAppPackageName()", stats)
+                .that(stats.getAppPackageName())
+                .isEqualTo(mAppPackageName);
+        expect.withMessage("%s.getSdkPackageName()", stats)
+                .that(stats.getSdkPackageName())
+                .isEqualTo(mSdkPackageName);
+        expect.withMessage("%s.getLatencyMillisecond()", stats)
+                .that(stats.getLatencyMillisecond())
+                .isEqualTo(LATENCY_MS);
+        expect.withMessage("%s.getResultCode()", stats)
+                .that(stats.getResultCode())
+                .isEqualTo(STATUS_INVALID_ARGUMENT);
     }
 
     @Test
     public void testNullSdkPackageName_throwsNPE() {
-        Assert.assertThrows("expect to throw null exception",
+        assertThrows(
                 NullPointerException.class,
-                () -> {
-                    new ApiCallStats.Builder()
-                            .setSdkPackageName(null)
-                            .build();
-                }
-        );
+                () -> new ApiCallStats.Builder().setSdkPackageName(null).build());
     }
 
     @Test
     public void testNullAppPackageName_throwsNPE() {
-        Assert.assertThrows("expect to throw null exception",
+        assertThrows(
                 NullPointerException.class,
-                () -> {
-                    new ApiCallStats.Builder()
-                            .setAppPackageName(null)
-                            .build();
-                }
-        );
+                () -> new ApiCallStats.Builder().setAppPackageName(null).build());
     }
 
     @Test
-    public void testIncompleteInputBuilder_throwsIAE() {
-        Assert.assertThrows("expect to throw Illegal Argument exception",
-                IllegalArgumentException.class,
-                () -> {
-                    new ApiCallStats.Builder()
-                            .build();
-                }
-        );
+    public void testBuild_noAppPackageName() {
+        assertThrows(
+                IllegalStateException.class,
+                () -> new ApiCallStats.Builder().setSdkPackageName("package.I.am").build());
+    }
+
+    @Test
+    public void testBuild_noSdkPackageName() {
+        assertThrows(
+                IllegalStateException.class,
+                () -> new ApiCallStats.Builder().setAppPackageName("package.I.am").build());
+    }
+
+    @Test
+    public void testToString() {
+        ApiCallStats stats =
+                new ApiCallStats.Builder()
+                        .setCode(AD_SERVICES_API_CALLED)
+                        .setApiClass(AD_SERVICES_API_CALLED__API_CLASS__TARGETING)
+                        .setApiName(AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS)
+                        .setAppPackageName(mAppPackageName)
+                        .setSdkPackageName(mSdkPackageName)
+                        .setLatencyMillisecond(LATENCY_MS)
+                        .setResultCode(STATUS_INVALID_ARGUMENT)
+                        .build();
+
+        String toString = stats.toString();
+
+        expect.that(toString).startsWith("ApiCallStats");
+        expect.that(toString).contains("Code=" + AD_SERVICES_API_CALLED);
+        expect.that(toString).contains("ApiClass=" + AD_SERVICES_API_CALLED__API_CLASS__TARGETING);
+        expect.that(toString).contains("ApiName=" + AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS);
+        expect.that(toString).contains("AppPackageName='" + mAppPackageName + "'");
+        expect.that(toString).contains("SdkPackageName='" + mSdkPackageName + "'");
+        expect.that(toString).contains("LatencyMillisecond=" + LATENCY_MS);
+        expect.that(toString).contains("ResultCode=" + STATUS_INVALID_ARGUMENT);
+    }
+
+    @Test
+    public void testEqualsHashCode() {
+        int baseCode = AD_SERVICES_API_CALLED;
+        int baseApiClass = AD_SERVICES_API_CALLED__API_CLASS__TARGETING;
+        int baseApiName = AD_SERVICES_API_CALLED__API_NAME__GET_TOPICS;
+        String baseAppPackageName = mAppPackageName;
+        String baseSdkPackageName = mSdkPackageName;
+        int baseLatencyMs = LATENCY_MS;
+        int resultCode = STATUS_INVALID_ARGUMENT;
+
+        ApiCallStats equals1 =
+                new ApiCallStats.Builder()
+                        .setCode(baseCode)
+                        .setApiClass(baseApiClass)
+                        .setApiName(baseApiName)
+                        .setAppPackageName(baseAppPackageName)
+                        .setSdkPackageName(baseSdkPackageName)
+                        .setLatencyMillisecond(baseLatencyMs)
+                        .setResultCode(resultCode)
+                        .build();
+
+        ApiCallStats equals2 =
+                new ApiCallStats.Builder()
+                        .setCode(baseCode)
+                        .setApiClass(baseApiClass)
+                        .setApiName(baseApiName)
+                        .setAppPackageName(baseAppPackageName)
+                        .setSdkPackageName(baseSdkPackageName)
+                        .setLatencyMillisecond(baseLatencyMs)
+                        .setResultCode(resultCode)
+                        .build();
+
+        ApiCallStats different1 =
+                new ApiCallStats.Builder()
+                        .setCode(baseCode + 42)
+                        .setApiClass(baseApiClass)
+                        .setApiName(baseApiName)
+                        .setAppPackageName(baseAppPackageName)
+                        .setSdkPackageName(baseSdkPackageName)
+                        .setLatencyMillisecond(baseLatencyMs)
+                        .setResultCode(resultCode)
+                        .build();
+
+        ApiCallStats different2 =
+                new ApiCallStats.Builder()
+                        .setCode(baseCode)
+                        .setApiClass(baseApiClass + 42)
+                        .setApiName(baseApiName)
+                        .setAppPackageName(baseAppPackageName)
+                        .setSdkPackageName(baseSdkPackageName)
+                        .setLatencyMillisecond(baseLatencyMs)
+                        .setResultCode(resultCode)
+                        .build();
+
+        ApiCallStats different3 =
+                new ApiCallStats.Builder()
+                        .setCode(baseCode)
+                        .setApiClass(baseApiClass)
+                        .setApiName(baseApiName + 42)
+                        .setAppPackageName(baseAppPackageName)
+                        .setSdkPackageName(baseSdkPackageName)
+                        .setLatencyMillisecond(baseLatencyMs)
+                        .setResultCode(resultCode)
+                        .build();
+
+        ApiCallStats different4 =
+                new ApiCallStats.Builder()
+                        .setCode(baseCode)
+                        .setApiClass(baseApiClass)
+                        .setApiName(baseApiName)
+                        .setAppPackageName(baseAppPackageName + ".doh")
+                        .setSdkPackageName(baseSdkPackageName)
+                        .setLatencyMillisecond(baseLatencyMs)
+                        .setResultCode(resultCode)
+                        .build();
+
+        ApiCallStats different5 =
+                new ApiCallStats.Builder()
+                        .setCode(baseCode)
+                        .setApiClass(baseApiClass)
+                        .setApiName(baseApiName)
+                        .setAppPackageName(baseAppPackageName)
+                        .setSdkPackageName(baseSdkPackageName + ".doh")
+                        .setLatencyMillisecond(baseLatencyMs)
+                        .setResultCode(resultCode)
+                        .build();
+
+        ApiCallStats different6 =
+                new ApiCallStats.Builder()
+                        .setCode(baseCode)
+                        .setApiClass(baseApiClass)
+                        .setApiName(baseApiName)
+                        .setAppPackageName(baseAppPackageName)
+                        .setSdkPackageName(baseSdkPackageName)
+                        .setLatencyMillisecond(baseLatencyMs + 42)
+                        .setResultCode(resultCode)
+                        .build();
+
+        ApiCallStats different7 =
+                new ApiCallStats.Builder()
+                        .setCode(baseCode)
+                        .setApiClass(baseApiClass)
+                        .setApiName(baseApiName)
+                        .setAppPackageName(baseAppPackageName)
+                        .setSdkPackageName(baseSdkPackageName)
+                        .setLatencyMillisecond(baseLatencyMs)
+                        .setResultCode(resultCode + 42)
+                        .build();
+
+        expectObjectsAreEqual(equals1, equals2);
+
+        expectObjectsAreNotEqual(equals1, null);
+        expectObjectsAreNotEqual(equals1, "STATS, Y U NO STRING?");
+
+        expectObjectsAreNotEqual(equals1, different1);
+        expectObjectsAreNotEqual(equals1, different2);
+        expectObjectsAreNotEqual(equals1, different3);
+        expectObjectsAreNotEqual(equals1, different4);
+        expectObjectsAreNotEqual(equals1, different5);
+        expectObjectsAreNotEqual(equals1, different6);
+        expectObjectsAreNotEqual(equals1, different7);
     }
 }