Implement custom audience validator and limit checker

Test: atest
Bug: 237679543
Bug: 221861025

Change-Id: I0c3212f32fd2fc285d7cfe98265b17187747f357
diff --git a/adservices/framework/java/android/adservices/customaudience/CustomAudience.java b/adservices/framework/java/android/adservices/customaudience/CustomAudience.java
index 77efdab..ebc0592 100644
--- a/adservices/framework/java/android/adservices/customaudience/CustomAudience.java
+++ b/adservices/framework/java/android/adservices/customaudience/CustomAudience.java
@@ -23,9 +23,6 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-
-import com.android.internal.util.Preconditions;
-
 import java.time.Instant;
 import java.util.List;
 import java.util.Objects;
@@ -472,23 +469,11 @@
             Objects.requireNonNull(mDailyUpdateUrl);
             Objects.requireNonNull(mBiddingLogicUrl);
 
-            if (mExpirationTime != null) {
-                Preconditions.checkArgument(mExpirationTime.isAfter(Instant.now()),
-                        "Expiration time must be in the future.");
-            }
-
-            if (mActivationTime != null && mExpirationTime != null) {
-                Preconditions.checkArgument(mExpirationTime.isAfter(mActivationTime),
-                        "Expiration time must be before activation time.");
-            }
-
             // To pass the API lint, we should not allow null Collection.
             if (mAds == null) {
                 mAds = List.of();
             }
 
-            // TODO(b/231997523): Add JSON field validation for user bidding signals.
-
             return new CustomAudience(this);
         }
     }
diff --git a/adservices/service-core/java/com/android/adservices/data/customaudience/DBCustomAudience.java b/adservices/service-core/java/com/android/adservices/data/customaudience/DBCustomAudience.java
index a292c35..ff9d66e 100644
--- a/adservices/service-core/java/com/android/adservices/data/customaudience/DBCustomAudience.java
+++ b/adservices/service-core/java/com/android/adservices/data/customaudience/DBCustomAudience.java
@@ -54,10 +54,6 @@
 @TypeConverters({DBCustomAudience.Converters.class})
 public class DBCustomAudience {
     public static final String TABLE_NAME = "custom_audience";
-    // Default to 60-day expiry
-    private static final Duration DEFAULT_EXPIRE_IN = Duration.ofDays(60);
-    private static final Duration MAX_ACTIVATE_IN = Duration.ofDays(60);
-    private static final Duration MAX_EXPIRE_IN = Duration.ofDays(60);
 
     @ColumnInfo(name = "owner", index = true)
     @NonNull
@@ -141,17 +137,22 @@
     /**
      * Parse parcelable {@link CustomAudience} to storage model {@link DBCustomAudience}.
      *
-     * @param parcelable     the service model.
+     * @param parcelable the service model.
      * @param callingAppName the app name where the call came from.
-     * @param currentTime    the timestamp when calling the method.
+     * @param currentTime the timestamp when calling the method.
+     * @param defaultExpireIn the default expiration from activation.
      * @return storage model
      */
     @NonNull
-    public static DBCustomAudience fromServiceObject(@NonNull CustomAudience parcelable,
-            @NonNull String callingAppName, @NonNull Instant currentTime) {
+    public static DBCustomAudience fromServiceObject(
+            @NonNull CustomAudience parcelable,
+            @NonNull String callingAppName,
+            @NonNull Instant currentTime,
+            @NonNull Duration defaultExpireIn) {
         Objects.requireNonNull(parcelable);
         Objects.requireNonNull(callingAppName);
         Objects.requireNonNull(currentTime);
+        Objects.requireNonNull(defaultExpireIn);
 
         String owner = Optional.ofNullable(parcelable.getOwner()).orElse(callingAppName);
 
@@ -159,15 +160,14 @@
         // Make it easier at query for activated CAs.
         Instant activationTime = Optional.ofNullable(parcelable.getActivationTime()).orElse(
                 currentTime);
+
         if (activationTime.isBefore(currentTime)) {
             activationTime = currentTime;
         }
-        Preconditions.checkArgument(!activationTime.isAfter(currentTime.plus(getMaxActivateIn())));
 
-        Instant expirationTime = Optional.ofNullable(parcelable.getExpirationTime())
-                .orElse(activationTime.plus(getDefaultExpireIn()));
-        Preconditions.checkArgument(expirationTime.isAfter(activationTime));
-        Preconditions.checkArgument(!expirationTime.isAfter(activationTime.plus(getMaxExpireIn())));
+        Instant expirationTime =
+                Optional.ofNullable(parcelable.getExpirationTime())
+                        .orElse(activationTime.plus(defaultExpireIn));
 
         Instant lastAdsAndBiddingDataUpdatedTime = parcelable.getAds().isEmpty()
                 || parcelable.getTrustedBiddingData() == null
@@ -230,36 +230,6 @@
     }
 
     /**
-     * See {@link #getExpirationTime()} for more information about expiration times.
-     *
-     * @return the default amount of time (in seconds) a {@link CustomAudience} object will live
-     * before being expiring and being removed
-     */
-    public static Duration getDefaultExpireIn() {
-        return DEFAULT_EXPIRE_IN;
-    }
-
-    /**
-     * See {@link #getActivationTime()} for more information about activation times.
-     *
-     * @return the maximum permitted difference in seconds between the {@link CustomAudience}
-     * object's creation time and its activation time
-     */
-    public static Duration getMaxActivateIn() {
-        return MAX_ACTIVATE_IN;
-    }
-
-    /**
-     * See {@link #getExpirationTime()} for more information about expiration times.
-     *
-     * @return the maximum permitted difference in seconds between the {@link CustomAudience}
-     * object's creation time and its activation time
-     */
-    public static Duration getMaxExpireIn() {
-        return MAX_EXPIRE_IN;
-    }
-
-    /**
      * The App that adds the user to this CustomAudience.
      * <p>Value must be &lt;App UID&gt;-&lt;package name&gt;.
      */
diff --git a/adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionConfigValidator.java b/adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionConfigValidator.java
index bcbabbe..b50dbe3 100644
--- a/adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionConfigValidator.java
+++ b/adservices/service-core/java/com/android/adservices/service/adselection/AdSelectionConfigValidator.java
@@ -22,8 +22,10 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.adservices.service.common.Validator;
 import com.android.internal.annotations.VisibleForTesting;
 
+import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 import com.google.common.net.InternetDomainName;
 
@@ -55,17 +57,15 @@
                     + " consistent.";
 
     @Override
-    public ImmutableList<String> getValidationViolations(
-            @NonNull AdSelectionConfig adSelectionConfig) {
-        ImmutableList.Builder<String> violations = new ImmutableList.Builder<>();
+    public void addValidation(
+            @NonNull AdSelectionConfig adSelectionConfig,
+            @NonNull ImmutableCollection.Builder<String> violations) {
         if (Objects.isNull(adSelectionConfig)) {
             violations.add("The adSelectionConfig should not be null.");
         }
         violations.addAll(
                 validateSellerAndSellerDecisionUrls(
                         adSelectionConfig.getSeller(), adSelectionConfig.getDecisionLogicUri()));
-
-        return violations.build();
     }
 
     /**
diff --git a/adservices/service-core/java/com/android/adservices/service/common/AdDataValidator.java b/adservices/service-core/java/com/android/adservices/service/common/AdDataValidator.java
new file mode 100644
index 0000000..2ded206
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/common/AdDataValidator.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 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.adservices.service.common;
+
+import android.adservices.common.AdData;
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/** Validator for a ad data instance. */
+public class AdDataValidator implements Validator<AdData> {
+    @VisibleForTesting public static final String VIOLATION_FORMAT = "For %s, %s";
+
+    @VisibleForTesting public static final String AD_DATA_CLASS_NAME = AdData.class.getName();
+    @VisibleForTesting public static final String RENDER_URI_FIELD_NAME = "render uri";
+    @VisibleForTesting public static final String METADATA_FIELD_NAME = "metadata";
+
+    @NonNull private final AdTechUriValidator mUriValidator;
+    @NonNull private final JsonValidator mMetadataValidator;
+
+    public AdDataValidator(@NonNull String adTechRole, @NonNull String adTechIdentifier) {
+        Objects.requireNonNull(adTechRole);
+        Objects.requireNonNull(adTechIdentifier);
+
+        mUriValidator =
+                new AdTechUriValidator(
+                        adTechRole, adTechIdentifier, AD_DATA_CLASS_NAME, RENDER_URI_FIELD_NAME);
+        mMetadataValidator = new JsonValidator(AD_DATA_CLASS_NAME, METADATA_FIELD_NAME);
+    }
+
+    /**
+     * Validate an ad data is valid.
+     *
+     * @param adData the ad data to be validated.
+     */
+    @Override
+    public void addValidation(
+            @NonNull AdData adData, @NonNull ImmutableCollection.Builder<String> violations) {
+        Objects.requireNonNull(adData);
+        Objects.requireNonNull(violations);
+
+        ImmutableCollection.Builder<String> adDataViolations = new ImmutableList.Builder<>();
+        mUriValidator.addValidation(adData.getRenderUri(), adDataViolations);
+        mMetadataValidator.addValidation(adData.getMetadata(), adDataViolations);
+
+        violations.addAll(
+                adDataViolations.build().stream()
+                        .map(violation -> String.format(VIOLATION_FORMAT, adData, violation))
+                        .collect(Collectors.toList()));
+    }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/common/AdTechIdentifierValidator.java b/adservices/service-core/java/com/android/adservices/service/common/AdTechIdentifierValidator.java
new file mode 100644
index 0000000..4b4c329
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/common/AdTechIdentifierValidator.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 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.adservices.service.common;
+
+import static com.android.adservices.service.common.ValidatorUtil.HTTPS_SCHEME;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.net.InternetDomainName;
+
+import java.util.Objects;
+
+/** Validation utility class for Ad Tech Identifier. */
+// TODO(b/239729221): Apply this to AdSelection
+public class AdTechIdentifierValidator implements Validator<String> {
+    @VisibleForTesting
+    public static final String IDENTIFIER_SHOULD_NOT_BE_NULL_OR_EMPTY =
+            "The %s's %s should not be null nor empty.";
+
+    @VisibleForTesting
+    public static final String IDENTIFIER_HAS_MISSING_DOMAIN_NAME =
+            "The %s's %s has missing domain name.";
+
+    @VisibleForTesting
+    public static final String IDENTIFIER_IS_AN_INVALID_DOMAIN_NAME =
+            "The %s's %s is an invalid domain name.";
+
+    @NonNull private final String mClassName;
+    @NonNull private final String mAdTechRole;
+
+    /**
+     * Constructs a validator which validates an ad tech identifier.
+     *
+     * @param className the class the name of the field came from, used in error string
+     *     construction.
+     * @param adTechRole the identifier's field name, used in error string construction.
+     */
+    public AdTechIdentifierValidator(@NonNull String className, @NonNull String adTechRole) {
+        Objects.requireNonNull(className);
+        Objects.requireNonNull(adTechRole);
+
+        mClassName = className;
+        mAdTechRole = adTechRole;
+    }
+
+    /**
+     * Validate an ad tech identifier:
+     *
+     * <ul>
+     *   <li>The identifier should not be empty or null.
+     *   <li>The identifier should not have a valid domain name.
+     *   <li>The identifier should be a domain name.
+     * </ul>
+     *
+     * @param adTechIdentifier the identifier to be validated
+     */
+    @Override
+    public void addValidation(
+            @Nullable String adTechIdentifier,
+            @NonNull ImmutableCollection.Builder<String> violations) {
+        Objects.requireNonNull(violations);
+
+        String host = Uri.parse(HTTPS_SCHEME + "://" + adTechIdentifier).getHost();
+        if (ValidatorUtil.isStringNullOrEmpty(adTechIdentifier)) {
+            violations.add(
+                    String.format(IDENTIFIER_SHOULD_NOT_BE_NULL_OR_EMPTY, mClassName, mAdTechRole));
+        } else if (Objects.isNull(host) || host.isEmpty()) {
+            violations.add(
+                    String.format(IDENTIFIER_HAS_MISSING_DOMAIN_NAME, mClassName, mAdTechRole));
+        } else if (!Objects.equals(host, adTechIdentifier)
+                || !InternetDomainName.isValid(adTechIdentifier)) {
+            violations.add(
+                    String.format(IDENTIFIER_IS_AN_INVALID_DOMAIN_NAME, mClassName, mAdTechRole));
+        }
+    }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/common/AdTechUriValidator.java b/adservices/service-core/java/com/android/adservices/service/common/AdTechUriValidator.java
new file mode 100644
index 0000000..8da99ce
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/common/AdTechUriValidator.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 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.adservices.service.common;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.collect.ImmutableCollection;
+
+import java.util.Objects;
+
+/** Validates an ad tech uri against an ad tech identifier. */
+// TODO(b/239729221): Apply this to AdSelection
+public class AdTechUriValidator implements Validator<Uri> {
+
+    @VisibleForTesting
+    public static final String URI_SHOULD_BE_SPECIFIED = "The %s's %s should be specified.";
+
+    @VisibleForTesting
+    public static final String URI_SHOULD_HAVE_PRESENT_HOST =
+            "The %s's %s should have present host.";
+
+    @VisibleForTesting
+    public static final String URI_SHOULD_USE_HTTPS = "The %s's %s should use HTTPS.";
+
+    @VisibleForTesting
+    public static final String IDENTIFIER_AND_URL_ARE_INCONSISTENT =
+            "The %s host name %s and the %s-provided %s's host name %s are not consistent.";
+
+    @NonNull public final String mAdTechRole;
+    @NonNull public final String mAdTechIdentifier;
+    @NonNull public final String mClassName;
+    @NonNull public final String mUriFieldName;
+
+    public AdTechUriValidator(
+            @NonNull String adTechRole,
+            @NonNull String adTechIdentifier,
+            @NonNull String className,
+            @NonNull String uriFieldName) {
+        Objects.requireNonNull(adTechRole);
+        Objects.requireNonNull(adTechIdentifier);
+        Objects.requireNonNull(className);
+        Objects.requireNonNull(uriFieldName);
+
+        mAdTechRole = adTechRole;
+        mAdTechIdentifier = adTechIdentifier;
+        mClassName = className;
+        mUriFieldName = uriFieldName;
+    }
+
+    /**
+     * Validate an uri uses HTTPS and under the ad tech identifier domain.
+     *
+     * @param uri the uri to be validated.
+     */
+    @Override
+    public void addValidation(
+            @Nullable Uri uri, @NonNull ImmutableCollection.Builder<String> violations) {
+        Objects.requireNonNull(violations);
+
+        if (Objects.isNull(uri)) {
+            violations.add(String.format(URI_SHOULD_BE_SPECIFIED, mClassName, mUriFieldName));
+        } else {
+            String uriHost = uri.getHost();
+            if (ValidatorUtil.isStringNullOrEmpty(uriHost)) {
+                violations.add(
+                        String.format(URI_SHOULD_HAVE_PRESENT_HOST, mClassName, mUriFieldName));
+            } else if (!ValidatorUtil.isStringNullOrEmpty(mAdTechIdentifier)
+                    && !mAdTechIdentifier.equalsIgnoreCase(uriHost)) {
+                violations.add(
+                        String.format(
+                                IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+                                mAdTechRole,
+                                mAdTechIdentifier,
+                                mAdTechRole,
+                                mUriFieldName,
+                                uriHost));
+            } else if (!ValidatorUtil.HTTPS_SCHEME.equalsIgnoreCase(uri.getScheme())) {
+                violations.add(String.format(URI_SHOULD_USE_HTTPS, mClassName, mUriFieldName));
+            }
+        }
+    }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/common/JsonValidator.java b/adservices/service-core/java/com/android/adservices/service/common/JsonValidator.java
new file mode 100644
index 0000000..5fbe00d
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/common/JsonValidator.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 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.adservices.service.common;
+
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import com.google.common.collect.ImmutableCollection;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Objects;
+
+/** Validation utility class for JSON fields. */
+public class JsonValidator implements Validator<String> {
+    @VisibleForTesting
+    public static final String SHOULD_BE_A_VALID_JSON = "%s's %s should be a valid json.";
+
+    @NonNull private final String mClassName;
+    @NonNull private final String mFieldName;
+
+    /**
+     * Constructs a validator which validates an ad tech identifier.
+     *
+     * @param className the class the field belong to
+     * @param fieldName the field name of the json
+     */
+    public JsonValidator(@NonNull String className, @NonNull String fieldName) {
+        Preconditions.checkStringNotEmpty(className);
+        Preconditions.checkStringNotEmpty(fieldName);
+
+        mClassName = className;
+        mFieldName = fieldName;
+    }
+
+    /**
+     * Validate a string field is a valid json.
+     *
+     * @param json the field to be validated
+     */
+    @Override
+    public void addValidation(
+            @NonNull String json, @NonNull ImmutableCollection.Builder<String> violations) {
+        Objects.requireNonNull(json);
+        Objects.requireNonNull(violations);
+
+        try {
+            new JSONObject(json);
+        } catch (JSONException jsonException) {
+            violations.add(String.format(SHOULD_BE_A_VALID_JSON, mClassName, mFieldName));
+        }
+    }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/adselection/Validator.java b/adservices/service-core/java/com/android/adservices/service/common/Validator.java
similarity index 70%
rename from adservices/service-core/java/com/android/adservices/service/adselection/Validator.java
rename to adservices/service-core/java/com/android/adservices/service/common/Validator.java
index 7c13910..7cea706 100644
--- a/adservices/service-core/java/com/android/adservices/service/adselection/Validator.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/Validator.java
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.adservices.service.adselection;
+package com.android.adservices.service.common;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
 
 import java.util.Collection;
 
@@ -24,6 +27,8 @@
  * @param <T> is the type name of the object instance to be validated.
  */
 public interface Validator<T> {
+    String EXCEPTION_MESSAGE_FORMAT = "Invalid object of type %s. The violations are: %s";
+
     /**
      * Validate the object instance of type T.
      *
@@ -36,8 +41,7 @@
         if (!violations.isEmpty()) {
             throw new IllegalArgumentException(
                     String.format(
-                            "Invalid object of type %s. The violations are: %s",
-                            object.getClass().getName(), violations));
+                            EXCEPTION_MESSAGE_FORMAT, object.getClass().getName(), violations));
         }
     }
 
@@ -48,5 +52,12 @@
      * @return an empty collection if the object is valid or a collection of strings describing all
      *     the encountered violations.
      */
-    Collection<String> getValidationViolations(T object);
+    default Collection<String> getValidationViolations(T object) {
+        ImmutableCollection.Builder<String> violations = new ImmutableList.Builder<>();
+        addValidation(object, violations);
+        return violations.build();
+    }
+
+    /** Validates the object and populate the violations. */
+    void addValidation(T object, ImmutableCollection.Builder<String> violations);
 }
diff --git a/adservices/service-core/java/com/android/adservices/service/common/ValidatorUtil.java b/adservices/service-core/java/com/android/adservices/service/common/ValidatorUtil.java
index c7fe1bb..4601aff 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/ValidatorUtil.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/ValidatorUtil.java
@@ -16,10 +16,21 @@
 
 package com.android.adservices.service.common;
 
+import androidx.annotation.Nullable;
+
+import java.util.Objects;
+
 /** Constants and utility method for data validation. */
 public class ValidatorUtil {
+    public static final String AD_TECH_ROLE_BUYER = "buyer";
+    public static final String AD_TECH_ROLE_SELLER = "seller";
 
     public static final String HTTPS_SCHEME = "https";
 
+    /** Returns true if the given string is null or empty or vise versa. */
+    public static boolean isStringNullOrEmpty(@Nullable String str) {
+        return Objects.isNull(str) || str.isEmpty();
+    }
+
     private ValidatorUtil() {}
 }
diff --git a/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceImpl.java b/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceImpl.java
index e5d942c..95306a1 100644
--- a/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceImpl.java
+++ b/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceImpl.java
@@ -24,11 +24,15 @@
 import com.android.adservices.data.customaudience.CustomAudienceDao;
 import com.android.adservices.data.customaudience.CustomAudienceDatabase;
 import com.android.adservices.data.customaudience.DBCustomAudience;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.common.Validator;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
 import java.time.Clock;
+import java.time.Duration;
 import java.time.Instant;
 import java.util.Objects;
 import java.util.Optional;
@@ -45,12 +49,24 @@
     private static CustomAudienceImpl sSingleton;
 
     private final CustomAudienceDao mCustomAudienceDao;
+    private final CustomAudienceQuantityChecker mCustomAudienceQuantityChecker;
+    private final Validator<CustomAudience> mCustomAudienceValidator;
     private final Clock mClock;
+    private final Duration mCustomAudienceDefaultExpireIn;
 
     @VisibleForTesting
-    public CustomAudienceImpl(@NonNull CustomAudienceDao customAudienceDao, @NonNull Clock clock) {
+    public CustomAudienceImpl(
+            @NonNull CustomAudienceDao customAudienceDao,
+            @NonNull CustomAudienceQuantityChecker customAudienceQuantityChecker,
+            @NonNull Validator<CustomAudience> customAudienceValidator,
+            @NonNull Clock clock,
+            @NonNull Flags flags) {
         mCustomAudienceDao = customAudienceDao;
+        mCustomAudienceQuantityChecker = customAudienceQuantityChecker;
+        mCustomAudienceValidator = customAudienceValidator;
         mClock = clock;
+        mCustomAudienceDefaultExpireIn =
+                Duration.ofMillis(flags.getFledgeCustomAudienceDefaultExpireInMs());
     }
 
     /**
@@ -63,10 +79,16 @@
         Objects.requireNonNull(context, "Context must be provided.");
         synchronized (SINGLETON_LOCK) {
             if (sSingleton == null) {
+                Flags flags = FlagsFactory.getFlags();
+                CustomAudienceDao customAudienceDao =
+                        CustomAudienceDatabase.getInstance(context).customAudienceDao();
                 sSingleton =
                         new CustomAudienceImpl(
-                                CustomAudienceDatabase.getInstance(context).customAudienceDao(),
-                                Clock.systemUTC());
+                                customAudienceDao,
+                                new CustomAudienceQuantityChecker(customAudienceDao, flags),
+                                CustomAudienceValidator.getInstance(context),
+                                Clock.systemUTC(),
+                                flags);
             }
             return sSingleton;
         }
@@ -81,10 +103,15 @@
         Objects.requireNonNull(customAudience);
         Instant currentTime = mClock.instant();
 
-        // TODO(b/231997523): Add JSON field validation.
+        mCustomAudienceQuantityChecker.check(customAudience);
+        mCustomAudienceValidator.validate(customAudience);
+
         DBCustomAudience dbCustomAudience =
                 DBCustomAudience.fromServiceObject(
-                        customAudience, "not.implemented.yet", currentTime);
+                        customAudience,
+                        "not.implemented.yet",
+                        currentTime,
+                        mCustomAudienceDefaultExpireIn);
 
         mCustomAudienceDao.insertOrOverwriteCustomAudience(
                 dbCustomAudience, customAudience.getDailyUpdateUrl());
diff --git a/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceQuantityChecker.java b/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceQuantityChecker.java
new file mode 100644
index 0000000..97fa635
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceQuantityChecker.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 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.adservices.service.customaudience;
+
+import android.adservices.customaudience.CustomAudience;
+import android.annotation.NonNull;
+
+import com.android.adservices.data.customaudience.CustomAudienceDao;
+import com.android.adservices.service.Flags;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/** Checks custom audience storage usage. */
+public class CustomAudienceQuantityChecker {
+    @VisibleForTesting
+    static final String CUSTOM_AUDIENCE_QUANTITY_CHECK_FAILED =
+            "Custom audience quantity check failed. %s";
+
+    @VisibleForTesting
+    static final String THE_MAX_NUMBER_OF_OWNER_ALLOWED_FOR_THE_DEVICE_HAD_REACHED =
+            "The max number of owner allowed for the device had reached.";
+
+    @VisibleForTesting
+    static final String THE_MAX_NUMBER_OF_CUSTOM_AUDIENCE_FOR_THE_DEVICE_HAD_REACHED =
+            "The max number of custom audience for the device had reached.";
+
+    @VisibleForTesting
+    static final String THE_MAX_NUMBER_OF_CUSTOM_AUDIENCE_FOR_THE_OWNER_HAD_REACHED =
+            "The max number of custom audience for the owner had reached.";
+
+    @NonNull private final CustomAudienceDao mCustomAudienceDao;
+    private final long mCustomAudienceMaxOwnerCount;
+    private final long mCustomAudienceMaxCount;
+    private final long mCustomAudiencePerAppMaxCount;
+
+    public CustomAudienceQuantityChecker(
+            @NonNull CustomAudienceDao customAudienceDao, @NonNull Flags flags) {
+        Objects.requireNonNull(customAudienceDao);
+        Objects.requireNonNull(flags);
+
+        mCustomAudienceDao = customAudienceDao;
+        mCustomAudienceMaxOwnerCount = flags.getFledgeCustomAudienceMaxOwnerCount();
+        mCustomAudienceMaxCount = flags.getFledgeCustomAudienceMaxCount();
+        mCustomAudiencePerAppMaxCount = flags.getFledgeCustomAudiencePerAppMaxCount();
+    }
+
+    /**
+     * Validates custom audience quantity:
+     *
+     * <ol>
+     *   <li>The total number of custom audience does not exceed max allowed.
+     *   <li>The total number of custom audience of an owner does not exceed max allowed.
+     *   <li>The total number of custom audience owner does not exceed max allowed.
+     * </ol>
+     *
+     * @param customAudience The custom audience really to be validated against.
+     */
+    public void check(@NonNull CustomAudience customAudience) {
+        Objects.requireNonNull(customAudience);
+
+        List<String> violations = new ArrayList<>();
+        CustomAudienceDao.CustomAudienceStats customAudienceStats =
+                mCustomAudienceDao.getCustomAudienceStats(customAudience.getOwner());
+        if (customAudienceStats.getPerOwnerCount() == 0
+                && customAudienceStats.getOwnerCount() >= mCustomAudienceMaxOwnerCount) {
+            violations.add(THE_MAX_NUMBER_OF_OWNER_ALLOWED_FOR_THE_DEVICE_HAD_REACHED);
+        }
+        if (customAudienceStats.getTotalCount() >= mCustomAudienceMaxCount) {
+            violations.add(THE_MAX_NUMBER_OF_CUSTOM_AUDIENCE_FOR_THE_DEVICE_HAD_REACHED);
+        }
+        if (customAudienceStats.getPerOwnerCount() >= mCustomAudiencePerAppMaxCount) {
+            violations.add(THE_MAX_NUMBER_OF_CUSTOM_AUDIENCE_FOR_THE_OWNER_HAD_REACHED);
+        }
+        if (!violations.isEmpty()) {
+            throw new IllegalArgumentException(
+                    String.format(CUSTOM_AUDIENCE_QUANTITY_CHECK_FAILED, violations));
+        }
+    }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceTimestampValidator.java b/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceTimestampValidator.java
new file mode 100644
index 0000000..0b60244
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceTimestampValidator.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 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.adservices.service.customaudience;
+
+import android.adservices.customaudience.CustomAudience;
+import android.annotation.NonNull;
+
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.common.Validator;
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.collect.ImmutableCollection;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Objects;
+
+/** Validates custom audience activation and expiration time */
+public class CustomAudienceTimestampValidator implements Validator<CustomAudience> {
+    @VisibleForTesting
+    static final String VIOLATION_EXPIRE_BEFORE_CURRENT_TIME =
+            "Custom audience must expire in the future, provided time is %s.";
+
+    @VisibleForTesting
+    static final String VIOLATION_EXPIRE_BEFORE_ACTIVATION =
+            "Custom audience activation time %s should not be after expiration time %s.";
+
+    @VisibleForTesting
+    static final String VIOLATION_EXPIRE_AFTER_MAX_EXPIRE_TIME =
+            "Custom audience expiration time should be within %s of activation time %s or"
+                    + " current time %s, which ever is later, provided time is %s.";
+
+    @VisibleForTesting
+    static final String VIOLATION_ACTIVATE_AFTER_MAX_ACTIVATE =
+            "Custom audience activation time should be within %s from current time %s, "
+                    + "provided time is %s.";
+
+    @NonNull private final Clock mClock;
+    @NonNull private final Duration mCustomAudienceMaxActivateIn;
+    @NonNull private final Duration mCustomAudienceMaxExpireIn;
+
+    public CustomAudienceTimestampValidator(@NonNull Clock clock, @NonNull Flags flags) {
+        Objects.requireNonNull(clock);
+        Objects.requireNonNull(flags);
+
+        mClock = clock;
+        mCustomAudienceMaxActivateIn =
+                Duration.ofMillis(flags.getFledgeCustomAudienceMaxActivationDelayInMs());
+        mCustomAudienceMaxExpireIn =
+                Duration.ofMillis(flags.getFledgeCustomAudienceMaxExpireInMs());
+    }
+
+    /**
+     * Validates custom audience activation time and expiration time.
+     *
+     * <ol>
+     *   <li>The activation delay should not beyond limit.
+     *   <li>The expiration time must be later then custom audience activation time.
+     *   <li>The expiration time must be in the future.
+     *   <li>The expiration time should not beyond certain time from activation.
+     * </ol>
+     *
+     * @param customAudience The custom audience need to be validated.
+     */
+    @Override
+    public void addValidation(
+            @NonNull CustomAudience customAudience,
+            @NonNull ImmutableCollection.Builder<String> violations) {
+        Objects.requireNonNull(customAudience);
+        Objects.requireNonNull(violations);
+
+        Instant currentTime = mClock.instant();
+        Instant activationTime = customAudience.getActivationTime();
+        Instant calculatedActivationTime =
+                activationTime.isBefore(currentTime) ? currentTime : activationTime;
+        Instant maxActivationTime = currentTime.plus(mCustomAudienceMaxActivateIn);
+        if (calculatedActivationTime.isAfter(maxActivationTime)) {
+            violations.add(
+                    String.format(
+                            VIOLATION_ACTIVATE_AFTER_MAX_ACTIVATE,
+                            mCustomAudienceMaxActivateIn,
+                            currentTime,
+                            customAudience.getActivationTime()));
+        }
+        Instant expirationTime = customAudience.getExpirationTime();
+        if (!customAudience.getExpirationTime().isAfter(currentTime)) {
+            violations.add(String.format(VIOLATION_EXPIRE_BEFORE_CURRENT_TIME, expirationTime));
+        } else if (!expirationTime.isAfter(activationTime)) {
+            violations.add(
+                    String.format(
+                            VIOLATION_EXPIRE_BEFORE_ACTIVATION, activationTime, expirationTime));
+        } else if (expirationTime.isAfter(
+                calculatedActivationTime.plus(mCustomAudienceMaxExpireIn))) {
+            violations.add(
+                    String.format(
+                            VIOLATION_EXPIRE_AFTER_MAX_EXPIRE_TIME,
+                            mCustomAudienceMaxExpireIn,
+                            customAudience.getActivationTime(),
+                            currentTime,
+                            expirationTime));
+        }
+    }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceValidator.java b/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceValidator.java
new file mode 100644
index 0000000..4d92123
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/customaudience/CustomAudienceValidator.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2022 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.adservices.service.customaudience;
+
+import android.adservices.common.AdData;
+import android.adservices.customaudience.CustomAudience;
+import android.annotation.NonNull;
+import android.content.Context;
+
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.common.AdDataValidator;
+import com.android.adservices.service.common.AdTechIdentifierValidator;
+import com.android.adservices.service.common.AdTechUriValidator;
+import com.android.adservices.service.common.JsonValidator;
+import com.android.adservices.service.common.Validator;
+import com.android.adservices.service.common.ValidatorUtil;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.collect.ImmutableCollection;
+
+import java.time.Clock;
+import java.util.Objects;
+
+/** Validator for Custom Audience. */
+public class CustomAudienceValidator implements Validator<CustomAudience> {
+    @VisibleForTesting
+    static final String CUSTOM_AUDIENCE_CLASS_NAME = CustomAudience.class.getName();
+
+    @VisibleForTesting static final String DAILY_UPDATE_URI_FIELD_NAME = "daily update uri";
+    @VisibleForTesting static final String BIDDING_LOGIC_URI_FIELD_NAME = "bidding logic uri";
+    @VisibleForTesting static final String USER_BIDDING_SIGNALS_FIELD_NAME = "user bidding signals";
+
+    private static final Object SINGLETON_LOCK = new Object();
+
+    @GuardedBy("SINGLETON_LOCK")
+    private static CustomAudienceValidator sSingleton;
+
+    @NonNull private final CustomAudienceTimestampValidator mCustomAudienceTimestampValidator;
+    @NonNull private final AdTechIdentifierValidator mBuyerValidator;
+    @NonNull private final JsonValidator mUserBiddingSignalsValidator;
+
+    @VisibleForTesting
+    public CustomAudienceValidator(
+            @NonNull CustomAudienceTimestampValidator customAudienceTimestampValidator,
+            @NonNull AdTechIdentifierValidator buyerValidator,
+            @NonNull JsonValidator userBiddingSignalsValidator) {
+        Objects.requireNonNull(customAudienceTimestampValidator);
+        Objects.requireNonNull(buyerValidator);
+        Objects.requireNonNull(userBiddingSignalsValidator);
+
+        mCustomAudienceTimestampValidator = customAudienceTimestampValidator;
+        mBuyerValidator = buyerValidator;
+        mUserBiddingSignalsValidator = userBiddingSignalsValidator;
+    }
+
+    @VisibleForTesting
+    public CustomAudienceValidator(@NonNull Clock clock, @NonNull Flags flags) {
+        this(
+                new CustomAudienceTimestampValidator(clock, flags),
+                new AdTechIdentifierValidator(
+                        CUSTOM_AUDIENCE_CLASS_NAME, ValidatorUtil.AD_TECH_ROLE_BUYER),
+                new JsonValidator(CUSTOM_AUDIENCE_CLASS_NAME, USER_BIDDING_SIGNALS_FIELD_NAME));
+    }
+
+    /**
+     * Gets an instance of {@link CustomAudienceValidator} to be used.
+     *
+     * <p>If no instance has been initialized yet, a new one will be created. Otherwise, the
+     * existing instance will be returned.
+     */
+    @NonNull
+    public static CustomAudienceValidator getInstance(@NonNull Context context) {
+        Objects.requireNonNull(context, "Context must be provided.");
+        synchronized (SINGLETON_LOCK) {
+            if (sSingleton == null) {
+                Flags flags = FlagsFactory.getFlags();
+                sSingleton = new CustomAudienceValidator(Clock.systemUTC(), flags);
+            }
+            return sSingleton;
+        }
+    }
+
+    /**
+     * Validates the custom audience.
+     *
+     * @param customAudience the instance to be validated.
+     */
+    @Override
+    public void addValidation(
+            @NonNull CustomAudience customAudience,
+            @NonNull ImmutableCollection.Builder<String> violations) {
+        Objects.requireNonNull(customAudience);
+        Objects.requireNonNull(violations);
+
+        validateFieldFormat(customAudience, violations);
+        mCustomAudienceTimestampValidator.addValidation(customAudience, violations);
+    }
+
+    private void validateFieldFormat(
+            CustomAudience customAudience, ImmutableCollection.Builder<String> violations) {
+        String buyer = customAudience.getBuyer();
+        mBuyerValidator.addValidation(buyer, violations);
+        new AdTechUriValidator(
+                        ValidatorUtil.AD_TECH_ROLE_BUYER,
+                        buyer,
+                        CUSTOM_AUDIENCE_CLASS_NAME,
+                        DAILY_UPDATE_URI_FIELD_NAME)
+                .addValidation(customAudience.getDailyUpdateUrl(), violations);
+        new AdTechUriValidator(
+                        ValidatorUtil.AD_TECH_ROLE_BUYER,
+                        buyer,
+                        CUSTOM_AUDIENCE_CLASS_NAME,
+                        BIDDING_LOGIC_URI_FIELD_NAME)
+                .addValidation(customAudience.getBiddingLogicUrl(), violations);
+        if (customAudience.getUserBiddingSignals() != null) {
+            mUserBiddingSignalsValidator.addValidation(
+                    customAudience.getUserBiddingSignals(), violations);
+        }
+        if (customAudience.getTrustedBiddingData() != null) {
+            new TrustedBiddingDataValidator(buyer)
+                    .addValidation(customAudience.getTrustedBiddingData(), violations);
+        }
+        AdDataValidator adDataValidator =
+                new AdDataValidator(ValidatorUtil.AD_TECH_ROLE_BUYER, buyer);
+        for (AdData adData : customAudience.getAds()) {
+            adDataValidator.addValidation(adData, violations);
+        }
+    }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/customaudience/TrustedBiddingDataValidator.java b/adservices/service-core/java/com/android/adservices/service/customaudience/TrustedBiddingDataValidator.java
new file mode 100644
index 0000000..f7669a2
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/customaudience/TrustedBiddingDataValidator.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 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.adservices.service.customaudience;
+
+import android.adservices.customaudience.TrustedBiddingData;
+import android.annotation.NonNull;
+
+import com.android.adservices.service.common.AdTechUriValidator;
+import com.android.adservices.service.common.Validator;
+import com.android.adservices.service.common.ValidatorUtil;
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.collect.ImmutableCollection;
+
+import java.util.Objects;
+
+/** Validator for a trusted bidding data. */
+public class TrustedBiddingDataValidator implements Validator<TrustedBiddingData> {
+    @VisibleForTesting
+    static final String TRUSTED_BIDDING_DATA_CLASS_NAME = TrustedBiddingData.class.getName();
+
+    @VisibleForTesting static final String TRUSTED_BIDDING_URI_FIELD_NAME = "trusted bidding uri";
+
+    @NonNull private final AdTechUriValidator mTrustedBiddingUriValidator;
+
+    public TrustedBiddingDataValidator(@NonNull String buyer) {
+        Objects.requireNonNull(buyer);
+
+        mTrustedBiddingUriValidator =
+                new AdTechUriValidator(
+                        ValidatorUtil.AD_TECH_ROLE_BUYER,
+                        buyer,
+                        TRUSTED_BIDDING_DATA_CLASS_NAME,
+                        TRUSTED_BIDDING_URI_FIELD_NAME);
+    }
+
+    /**
+     * Validate a trusted bidding data, the trusted bidding uri should be in buyer domain and should
+     * be a valid uri.
+     *
+     * @param trustedBiddingData the trusted bidding data to be validated.
+     */
+    @Override
+    public void addValidation(
+            @NonNull TrustedBiddingData trustedBiddingData,
+            @NonNull ImmutableCollection.Builder<String> violations) {
+        Objects.requireNonNull(trustedBiddingData);
+        Objects.requireNonNull(violations);
+
+        mTrustedBiddingUriValidator.addValidation(
+                trustedBiddingData.getTrustedBiddingUrl(), violations);
+    }
+}
diff --git a/adservices/tests/unittest/framework/src/android/adservices/customaudience/CustomAudienceTest.java b/adservices/tests/unittest/framework/src/android/adservices/customaudience/CustomAudienceTest.java
index c22154f..57c204c 100644
--- a/adservices/tests/unittest/framework/src/android/adservices/customaudience/CustomAudienceTest.java
+++ b/adservices/tests/unittest/framework/src/android/adservices/customaudience/CustomAudienceTest.java
@@ -207,33 +207,6 @@
     }
 
     @Test
-    public void testSetInvalidBeforeNowExpirationTimeCustomAudienceFails() {
-        assertThrows(
-                IllegalArgumentException.class,
-                () -> {
-                    // The expiry is in the past
-                    CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
-                            .setExpirationTime(
-                                    CustomAudienceFixture.INVALID_BEFORE_NOW_EXPIRATION_TIME)
-                            .build();
-                });
-    }
-
-    @Test
-    public void testSetInvalidBeforeDelayedExpirationTimeCustomAudienceFails() {
-        assertThrows(
-                IllegalArgumentException.class,
-                () -> {
-                    // The activation time is delayed, but the CA expires before it activates
-                    CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
-                            .setActivationTime(CustomAudienceFixture.VALID_DELAYED_ACTIVATION_TIME)
-                            .setExpirationTime(
-                                    CustomAudienceFixture.INVALID_BEFORE_DELAYED_EXPIRATION_TIME)
-                            .build();
-                });
-    }
-
-    @Test
     public void testBuildNullAdsCustomAudienceSuccess() {
         // Ads are not set, so the CustomAudience gets built with empty list.
         CustomAudience nullAdsCustomAudience =
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/customaudience/DBCustomAudienceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/customaudience/DBCustomAudienceTest.java
index 8865d2c..e92282e 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/data/customaudience/DBCustomAudienceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/customaudience/DBCustomAudienceTest.java
@@ -53,7 +53,8 @@
                         CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
                                 .build(),
                         CALLING_APP_NAME,
-                        CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI));
+                        CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
+                        DEFAULT_EXPIRE_IN));
     }
 
     @Test
@@ -64,7 +65,8 @@
                         DBCustomAudience.fromServiceObject(
                                 null,
                                 CALLING_APP_NAME,
-                                CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI));
+                                CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
+                                DEFAULT_EXPIRE_IN));
     }
 
     @Test
@@ -77,7 +79,8 @@
                                                 CommonFixture.VALID_BUYER)
                                         .build(),
                                 null,
-                                CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI));
+                                CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
+                                DEFAULT_EXPIRE_IN));
     }
 
     @Test
@@ -90,6 +93,21 @@
                                                 CommonFixture.VALID_BUYER)
                                         .build(),
                                 CALLING_APP_NAME,
+                                null,
+                                DEFAULT_EXPIRE_IN));
+    }
+
+    @Test
+    public void testFromServiceObject_nullDefaultExpireIn() {
+        assertThrows(
+                NullPointerException.class,
+                () ->
+                        DBCustomAudience.fromServiceObject(
+                                CustomAudienceFixture.getValidBuilderForBuyer(
+                                                CommonFixture.VALID_BUYER)
+                                        .build(),
+                                CALLING_APP_NAME,
+                                CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
                                 null));
     }
 
@@ -105,7 +123,8 @@
                                 .setAds(null)
                                 .build(),
                         CALLING_APP_NAME,
-                        CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI));
+                        CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
+                        DEFAULT_EXPIRE_IN));
     }
 
     @Test
@@ -121,7 +140,8 @@
                                                 200))
                                 .build(),
                         CALLING_APP_NAME,
-                        CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI));
+                        CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
+                        DEFAULT_EXPIRE_IN));
     }
 
     @Test
@@ -135,7 +155,8 @@
                                 .setActivationTime(null)
                                 .build(),
                         CALLING_APP_NAME,
-                        CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI));
+                        CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
+                        DEFAULT_EXPIRE_IN));
     }
 
     @Test
@@ -150,7 +171,8 @@
                                 .setExpirationTime(null)
                                 .build(),
                         CALLING_APP_NAME,
-                        CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI));
+                        CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
+                        DEFAULT_EXPIRE_IN));
     }
 
     @Test
@@ -164,7 +186,8 @@
                                 .setOwner(null)
                                 .build(),
                         CALLING_APP_NAME,
-                        CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI));
+                        CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
+                        DEFAULT_EXPIRE_IN));
     }
 
     @Test
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdDataValidatorTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdDataValidatorTest.java
new file mode 100644
index 0000000..e83d470
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdDataValidatorTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 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.adservices.service.common;
+
+import android.adservices.common.AdData;
+import android.adservices.common.AdDataFixture;
+import android.adservices.common.CommonFixture;
+import android.net.Uri;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class AdDataValidatorTest {
+
+    AdDataValidator mValidator =
+            new AdDataValidator(ValidatorUtil.AD_TECH_ROLE_BUYER, CommonFixture.VALID_BUYER);
+
+    @Test
+    public void testValidAdData() {
+        Assert.assertTrue(
+                mValidator
+                        .getValidationViolations(
+                                AdDataFixture.getValidAdsByBuyer(CommonFixture.VALID_BUYER).get(0))
+                        .isEmpty());
+    }
+
+    @Test
+    public void testInvalidUri() {
+        String uriHost = "b.com";
+        AdData adData =
+                new AdData.Builder()
+                        .setRenderUri(Uri.parse("https://" + uriHost + "/aaa"))
+                        .setMetadata("{\"a\":1}")
+                        .build();
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mValidator.getValidationViolations(adData),
+                String.format(
+                        AdDataValidator.VIOLATION_FORMAT,
+                        adData,
+                        String.format(
+                                AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+                                ValidatorUtil.AD_TECH_ROLE_BUYER,
+                                CommonFixture.VALID_BUYER,
+                                ValidatorUtil.AD_TECH_ROLE_BUYER,
+                                AdDataValidator.RENDER_URI_FIELD_NAME,
+                                uriHost)));
+    }
+
+    @Test
+    public void testInvalidMetadata() {
+        AdData adData =
+                new AdData.Builder()
+                        .setRenderUri(Uri.parse("https://" + CommonFixture.VALID_BUYER + "/aaa"))
+                        .setMetadata("invalid[json]field")
+                        .build();
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mValidator.getValidationViolations(adData),
+                String.format(
+                        AdDataValidator.VIOLATION_FORMAT,
+                        adData,
+                        String.format(
+                                JsonValidator.SHOULD_BE_A_VALID_JSON,
+                                AdDataValidator.AD_DATA_CLASS_NAME,
+                                AdDataValidator.METADATA_FIELD_NAME)));
+    }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdTechIdentifierValidatorTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdTechIdentifierValidatorTest.java
new file mode 100644
index 0000000..0934b2c
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdTechIdentifierValidatorTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 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.adservices.service.common;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class AdTechIdentifierValidatorTest {
+    private static final String CLASS_NAME = "class";
+    private static final String FIELD_NAME = "field";
+
+    private final AdTechIdentifierValidator mAdTechIdentifierValidator =
+            new AdTechIdentifierValidator(CLASS_NAME, FIELD_NAME);
+
+    @Test
+    public void testValidIdentifier() {
+        Assert.assertTrue(
+                mAdTechIdentifierValidator.getValidationViolations("domain.com").isEmpty());
+    }
+
+    @Test
+    public void testNullIdentifier() {
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mAdTechIdentifierValidator.getValidationViolations(null),
+                String.format(
+                        AdTechIdentifierValidator.IDENTIFIER_SHOULD_NOT_BE_NULL_OR_EMPTY,
+                        CLASS_NAME,
+                        FIELD_NAME));
+    }
+
+    @Test
+    public void testEmptyIdentifier() {
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mAdTechIdentifierValidator.getValidationViolations(""),
+                String.format(
+                        AdTechIdentifierValidator.IDENTIFIER_SHOULD_NOT_BE_NULL_OR_EMPTY,
+                        CLASS_NAME,
+                        FIELD_NAME));
+    }
+
+    @Test
+    public void testMissingHost() {
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mAdTechIdentifierValidator.getValidationViolations("test@"),
+                String.format(
+                        AdTechIdentifierValidator.IDENTIFIER_HAS_MISSING_DOMAIN_NAME,
+                        CLASS_NAME,
+                        FIELD_NAME));
+    }
+
+    @Test
+    public void testMissingDomainIdentifier() {
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mAdTechIdentifierValidator.getValidationViolations("a/b/c"),
+                String.format(
+                        AdTechIdentifierValidator.IDENTIFIER_IS_AN_INVALID_DOMAIN_NAME,
+                        CLASS_NAME,
+                        FIELD_NAME));
+    }
+
+    @Test
+    public void testDomainHasPort() {
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mAdTechIdentifierValidator.getValidationViolations("localhost:3000"),
+                String.format(
+                        AdTechIdentifierValidator.IDENTIFIER_IS_AN_INVALID_DOMAIN_NAME,
+                        CLASS_NAME,
+                        FIELD_NAME));
+    }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdTechUriValidatorTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdTechUriValidatorTest.java
new file mode 100644
index 0000000..c8ebd83
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/AdTechUriValidatorTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 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.adservices.service.common;
+
+import android.adservices.common.CommonFixture;
+import android.net.Uri;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class AdTechUriValidatorTest {
+    private static final String CLASS_NAME = "class";
+    private static final String URI_FIELD_NAME = "field";
+
+    private final AdTechUriValidator mValidator =
+            new AdTechUriValidator(
+                    ValidatorUtil.AD_TECH_ROLE_BUYER,
+                    CommonFixture.VALID_BUYER,
+                    CLASS_NAME,
+                    URI_FIELD_NAME);
+
+    @Test
+    public void testValidUri() {
+        Assert.assertTrue(
+                mValidator
+                        .getValidationViolations(
+                                Uri.parse("https://" + CommonFixture.VALID_BUYER + "/valid/uri"))
+                        .isEmpty());
+    }
+
+    @Test
+    public void testNullUri() {
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mValidator.getValidationViolations(null),
+                String.format(
+                        AdTechUriValidator.URI_SHOULD_BE_SPECIFIED, CLASS_NAME, URI_FIELD_NAME));
+    }
+
+    @Test
+    public void testNoHostUri() {
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mValidator.getValidationViolations(Uri.parse("/a/b/c")),
+                String.format(
+                        AdTechUriValidator.URI_SHOULD_HAVE_PRESENT_HOST,
+                        CLASS_NAME,
+                        URI_FIELD_NAME));
+    }
+
+    @Test
+    public void testNotMatchHost() {
+        String uriHost = "buy.com";
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mValidator.getValidationViolations(Uri.parse("https://" + uriHost + "/not/match")),
+                String.format(
+                        AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+                        ValidatorUtil.AD_TECH_ROLE_BUYER,
+                        CommonFixture.VALID_BUYER,
+                        ValidatorUtil.AD_TECH_ROLE_BUYER,
+                        URI_FIELD_NAME,
+                        uriHost));
+    }
+
+    @Test
+    public void testNotHttpsHost() {
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mValidator.getValidationViolations(
+                        Uri.parse("http://" + CommonFixture.VALID_BUYER + "/not/https/")),
+                String.format(AdTechUriValidator.URI_SHOULD_USE_HTTPS, CLASS_NAME, URI_FIELD_NAME));
+    }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeE2ETest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeE2ETest.java
index fa18cac..a397d8f 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeE2ETest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/FledgeE2ETest.java
@@ -58,7 +58,9 @@
 import com.android.adservices.service.FlagsFactory;
 import com.android.adservices.service.adselection.AdSelectionServiceImpl;
 import com.android.adservices.service.customaudience.CustomAudienceImpl;
+import com.android.adservices.service.customaudience.CustomAudienceQuantityChecker;
 import com.android.adservices.service.customaudience.CustomAudienceServiceImpl;
+import com.android.adservices.service.customaudience.CustomAudienceValidator;
 import com.android.adservices.service.devapi.DevContext;
 import com.android.adservices.service.devapi.DevContextFilter;
 import com.android.adservices.service.stats.AdServicesLogger;
@@ -170,7 +172,12 @@
                 new CustomAudienceServiceImpl(
                         CONTEXT,
                         new CustomAudienceImpl(
-                                mCustomAudienceDao, CommonFixture.FIXED_CLOCK_TRUNCATED_TO_MILLI),
+                                mCustomAudienceDao,
+                                new CustomAudienceQuantityChecker(mCustomAudienceDao, mFlags),
+                                new CustomAudienceValidator(
+                                        CommonFixture.FIXED_CLOCK_TRUNCATED_TO_MILLI, mFlags),
+                                CommonFixture.FIXED_CLOCK_TRUNCATED_TO_MILLI,
+                                mFlags),
                         mDevContextFilter,
                         MoreExecutors.newDirectExecutorService(),
                         mAdServicesLogger);
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/JsonValidatorTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/JsonValidatorTest.java
new file mode 100644
index 0000000..f7b872e
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/JsonValidatorTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 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.adservices.service.common;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class JsonValidatorTest {
+    private static final String CLASS_NAME = "class";
+    private static final String FIELD_NAME = "field";
+
+    private final JsonValidator mJsonValidator = new JsonValidator(CLASS_NAME, FIELD_NAME);
+
+    @Test
+    public void testNullJson() {
+        assertThrows(
+                NullPointerException.class,
+                () -> {
+                    mJsonValidator.getValidationViolations(null);
+                });
+    }
+
+    @Test
+    public void testValidJsonObject() {
+        assertTrue(mJsonValidator.getValidationViolations("{\"a\":5}").isEmpty());
+    }
+
+    @Test
+    public void testValidJsonArray() {
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mJsonValidator.getValidationViolations("[\"a\", \"b\"]"),
+                String.format(JsonValidator.SHOULD_BE_A_VALID_JSON, CLASS_NAME, FIELD_NAME));
+    }
+
+    @Test
+    public void testInvalidJson() {
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mJsonValidator.getValidationViolations("invalid[json]field"),
+                String.format(JsonValidator.SHOULD_BE_A_VALID_JSON, CLASS_NAME, FIELD_NAME));
+    }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/ValidatorTestUtil.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/ValidatorTestUtil.java
new file mode 100644
index 0000000..10dbca6
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/ValidatorTestUtil.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 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.adservices.service.common;
+
+import org.junit.Assert;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+public class ValidatorTestUtil {
+    public static void assertViolationContaiinsOnly(
+            Collection<String> errors, String... expectedErrors) {
+        List<String> expectedErrorsList = Arrays.asList(expectedErrors);
+        Assert.assertEquals(
+                String.format("expected %s / actual %s", expectedErrorsList, errors),
+                expectedErrors.length,
+                errors.size());
+        Assert.assertTrue(
+                String.format("expected %s / actual %s", expectedErrorsList, errors),
+                errors.containsAll(expectedErrorsList));
+        Assert.assertTrue(
+                String.format("expected %s / actual %s", expectedErrorsList, errors),
+                expectedErrorsList.containsAll(errors));
+    }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceImplTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceImplTest.java
index 990a41e..b8c5a5a 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceImplTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceImplTest.java
@@ -28,6 +28,7 @@
 import com.android.adservices.customaudience.DBCustomAudienceFixture;
 import com.android.adservices.data.customaudience.CustomAudienceDao;
 import com.android.adservices.data.customaudience.DBCustomAudience;
+import com.android.adservices.service.common.Validator;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -47,13 +48,21 @@
 
     @Mock
     private CustomAudienceDao mCustomAudienceDao;
+    @Mock private CustomAudienceQuantityChecker mCustomAudienceQuantityChecker;
+    @Mock private Validator<CustomAudience> mCustomAudienceValidator;
     @Mock private Clock mClock;
 
     public CustomAudienceImpl mImpl;
 
     @Before
     public void setup() {
-        mImpl = new CustomAudienceImpl(mCustomAudienceDao, mClock);
+        mImpl =
+                new CustomAudienceImpl(
+                        mCustomAudienceDao,
+                        mCustomAudienceQuantityChecker,
+                        mCustomAudienceValidator,
+                        mClock,
+                        CommonFixture.FLAGS_FOR_TEST);
     }
 
     @Test
@@ -69,7 +78,9 @@
                         CustomAudienceFixture.getValidDailyUpdateUriByBuyer(
                                 CommonFixture.VALID_BUYER));
         verify(mClock).instant();
-        verifyNoMoreInteractions(mClock, mCustomAudienceDao);
+        verify(mCustomAudienceQuantityChecker).check(VALID_CUSTOM_AUDIENCE);
+        verify(mCustomAudienceValidator).validate(VALID_CUSTOM_AUDIENCE);
+        verifyNoMoreInteractions(mClock, mCustomAudienceDao, mCustomAudienceValidator);
     }
 
     @Test
@@ -85,6 +96,10 @@
                         CommonFixture.VALID_BUYER,
                         CustomAudienceFixture.VALID_NAME);
 
-        verifyNoMoreInteractions(mClock, mCustomAudienceDao);
+        verifyNoMoreInteractions(
+                mClock,
+                mCustomAudienceDao,
+                mCustomAudienceQuantityChecker,
+                mCustomAudienceValidator);
     }
 }
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceQuantityCheckerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceQuantityCheckerTest.java
new file mode 100644
index 0000000..68fd667
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceQuantityCheckerTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2022 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.adservices.service.customaudience;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.adservices.common.CommonFixture;
+import android.adservices.customaudience.CustomAudienceFixture;
+
+import com.android.adservices.data.customaudience.CustomAudienceDao;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.List;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CustomAudienceQuantityCheckerTest {
+    private static final Flags FLAGS = FlagsFactory.getFlagsForTest();
+
+    @Mock private CustomAudienceDao mCustomAudienceDao;
+
+    private CustomAudienceQuantityChecker mChecker;
+
+    @Before
+    public void setup() {
+        mChecker = new CustomAudienceQuantityChecker(mCustomAudienceDao, FLAGS);
+    }
+
+    @Test
+    public void testNullCustomAudience_throwNPE() {
+        assertThrows(
+                NullPointerException.class,
+                () -> {
+                    mChecker.check(null);
+                });
+        verifyNoMoreInteractions(mCustomAudienceDao);
+    }
+
+    @Test
+    public void testExistOwnerAndOwnerReachMax_success() {
+        when(mCustomAudienceDao.getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER))
+                .thenReturn(
+                        new CustomAudienceDao.CustomAudienceStats(
+                                CustomAudienceFixture.VALID_OWNER,
+                                20L,
+                                1L,
+                                FLAGS.getFledgeCustomAudienceMaxOwnerCount()));
+        mChecker.check(
+                CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER).build());
+        verify(mCustomAudienceDao).getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER);
+        verifyNoMoreInteractions(mCustomAudienceDao);
+    }
+
+    @Test
+    public void testOwnerExceedMax() {
+        when(mCustomAudienceDao.getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER))
+                .thenReturn(
+                        new CustomAudienceDao.CustomAudienceStats(
+                                CustomAudienceFixture.VALID_OWNER,
+                                20L,
+                                0L,
+                                FLAGS.getFledgeCustomAudienceMaxOwnerCount()));
+
+        assertViolations(
+                assertThrows(
+                        IllegalArgumentException.class,
+                        () -> {
+                            mChecker.check(
+                                    CustomAudienceFixture.getValidBuilderForBuyer(
+                                                    CommonFixture.VALID_BUYER)
+                                            .build());
+                        }),
+                CustomAudienceQuantityChecker
+                        .THE_MAX_NUMBER_OF_OWNER_ALLOWED_FOR_THE_DEVICE_HAD_REACHED);
+
+        verify(mCustomAudienceDao).getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER);
+        verifyNoMoreInteractions(mCustomAudienceDao);
+    }
+
+    @Test
+    public void testTotalCountExceedMax() {
+        when(mCustomAudienceDao.getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER))
+                .thenReturn(
+                        new CustomAudienceDao.CustomAudienceStats(
+                                CustomAudienceFixture.VALID_OWNER,
+                                FLAGS.getFledgeCustomAudienceMaxCount(),
+                                0L,
+                                1L));
+        assertViolations(
+                assertThrows(
+                        IllegalArgumentException.class,
+                        () ->
+                                mChecker.check(
+                                        CustomAudienceFixture.getValidBuilderForBuyer(
+                                                        CommonFixture.VALID_BUYER)
+                                                .build())),
+                CustomAudienceQuantityChecker
+                        .THE_MAX_NUMBER_OF_CUSTOM_AUDIENCE_FOR_THE_DEVICE_HAD_REACHED);
+        verify(mCustomAudienceDao).getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER);
+        verifyNoMoreInteractions(mCustomAudienceDao);
+    }
+
+    @Test
+    public void testPerOwnerCountExceedMax() {
+        when(mCustomAudienceDao.getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER))
+                .thenReturn(
+                        new CustomAudienceDao.CustomAudienceStats(
+                                CustomAudienceFixture.VALID_OWNER,
+                                20L,
+                                FLAGS.getFledgeCustomAudiencePerAppMaxCount(),
+                                1L));
+        assertViolations(
+                assertThrows(
+                        IllegalArgumentException.class,
+                        () -> {
+                            mChecker.check(
+                                    CustomAudienceFixture.getValidBuilderForBuyer(
+                                                    CommonFixture.VALID_BUYER)
+                                            .build());
+                        }),
+                CustomAudienceQuantityChecker
+                        .THE_MAX_NUMBER_OF_CUSTOM_AUDIENCE_FOR_THE_OWNER_HAD_REACHED);
+
+        verify(mCustomAudienceDao).getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER);
+        verifyNoMoreInteractions(mCustomAudienceDao);
+    }
+
+    @Test
+    public void testAllGood() {
+        when(mCustomAudienceDao.getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER))
+                .thenReturn(
+                        new CustomAudienceDao.CustomAudienceStats(
+                                CustomAudienceFixture.VALID_OWNER, 0L, 0L, 0L));
+        mChecker.check(
+                CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER).build());
+
+        verify(mCustomAudienceDao).getCustomAudienceStats(CustomAudienceFixture.VALID_OWNER);
+        verifyNoMoreInteractions(mCustomAudienceDao);
+    }
+
+    private void assertViolations(Exception exception, String... violations) {
+        assertEquals(
+                String.format(
+                        CustomAudienceQuantityChecker.CUSTOM_AUDIENCE_QUANTITY_CHECK_FAILED,
+                        List.of(violations)),
+                exception.getMessage());
+    }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceServiceEndToEndTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceServiceEndToEndTest.java
index 37f4118..0d8ed22 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceServiceEndToEndTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceServiceEndToEndTest.java
@@ -120,11 +120,22 @@
                         .build()
                         .customAudienceDao();
 
+        CustomAudienceQuantityChecker customAudienceQuantityChecker =
+                new CustomAudienceQuantityChecker(mCustomAudienceDao, CommonFixture.FLAGS_FOR_TEST);
+
+        CustomAudienceValidator customAudienceValidator =
+                new CustomAudienceValidator(
+                        CommonFixture.FIXED_CLOCK_TRUNCATED_TO_MILLI, CommonFixture.FLAGS_FOR_TEST);
+
         mService =
                 new CustomAudienceServiceImpl(
                         CONTEXT,
                         new CustomAudienceImpl(
-                                mCustomAudienceDao, CommonFixture.FIXED_CLOCK_TRUNCATED_TO_MILLI),
+                                mCustomAudienceDao,
+                                customAudienceQuantityChecker,
+                                customAudienceValidator,
+                                CommonFixture.FIXED_CLOCK_TRUNCATED_TO_MILLI,
+                                CommonFixture.FLAGS_FOR_TEST),
                         mDevContextFilter,
                         MoreExecutors.newDirectExecutorService(),
                         mAdServicesLogger);
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceTimestampValidatorTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceTimestampValidatorTest.java
new file mode 100644
index 0000000..e0baf20
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceTimestampValidatorTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 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.adservices.service.customaudience;
+
+import android.adservices.common.CommonFixture;
+import android.adservices.customaudience.CustomAudienceFixture;
+
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.common.ValidatorTestUtil;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class CustomAudienceTimestampValidatorTest {
+    private static final Flags FLAGS = FlagsFactory.getFlagsForTest();
+    private final CustomAudienceTimestampValidator mValidator =
+            new CustomAudienceTimestampValidator(
+                    CommonFixture.FIXED_CLOCK_TRUNCATED_TO_MILLI, FLAGS);
+
+    @Test
+    public void testAllValidTimes() {
+        Assert.assertTrue(
+                mValidator
+                        .getValidationViolations(
+                                CustomAudienceFixture.getValidBuilderForBuyer(
+                                                CommonFixture.VALID_BUYER)
+                                        .build())
+                        .isEmpty());
+    }
+
+    @Test
+    public void testActivationTimeBeyondMaxAndExpireBeforeActivation() {
+
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mValidator.getValidationViolations(
+                        CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
+                                .setActivationTime(
+                                        CustomAudienceFixture.INVALID_DELAYED_ACTIVATION_TIME)
+                                .build()),
+                String.format(
+                        CustomAudienceTimestampValidator.VIOLATION_ACTIVATE_AFTER_MAX_ACTIVATE,
+                        CustomAudienceFixture.CUSTOM_AUDIENCE_MAX_ACTIVATION_DELAY_IN,
+                        CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
+                        CustomAudienceFixture.INVALID_DELAYED_ACTIVATION_TIME),
+                String.format(
+                        CustomAudienceTimestampValidator.VIOLATION_EXPIRE_BEFORE_ACTIVATION,
+                        CustomAudienceFixture.INVALID_DELAYED_ACTIVATION_TIME,
+                        CustomAudienceFixture.VALID_EXPIRATION_TIME));
+    }
+
+    @Test
+    public void testExpirationTimeBeforeNow() {
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mValidator.getValidationViolations(
+                        CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
+                                .setExpirationTime(
+                                        CustomAudienceFixture.INVALID_BEFORE_NOW_EXPIRATION_TIME)
+                                .build()),
+                String.format(
+                        CustomAudienceTimestampValidator.VIOLATION_EXPIRE_BEFORE_CURRENT_TIME,
+                        CustomAudienceFixture.INVALID_BEFORE_NOW_EXPIRATION_TIME));
+    }
+
+    @Test
+    public void testExpirationTimeBeforeActivation() {
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mValidator.getValidationViolations(
+                        CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
+                                .setExpirationTime(
+                                        CustomAudienceFixture
+                                                .INVALID_BEFORE_DELAYED_EXPIRATION_TIME)
+                                .setActivationTime(
+                                        CustomAudienceFixture.VALID_DELAYED_ACTIVATION_TIME)
+                                .build()),
+                String.format(
+                        CustomAudienceTimestampValidator.VIOLATION_EXPIRE_BEFORE_ACTIVATION,
+                        CustomAudienceFixture.VALID_DELAYED_ACTIVATION_TIME,
+                        CustomAudienceFixture.INVALID_BEFORE_DELAYED_EXPIRATION_TIME));
+    }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceValidatorTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceValidatorTest.java
new file mode 100644
index 0000000..7273f58
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/CustomAudienceValidatorTest.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2022 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.adservices.service.customaudience;
+
+import android.adservices.common.AdData;
+import android.adservices.common.AdDataFixture;
+import android.adservices.common.CommonFixture;
+import android.adservices.customaudience.CustomAudienceFixture;
+import android.adservices.customaudience.TrustedBiddingDataFixture;
+
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.common.AdDataValidator;
+import com.android.adservices.service.common.AdTechIdentifierValidator;
+import com.android.adservices.service.common.AdTechUriValidator;
+import com.android.adservices.service.common.JsonValidator;
+import com.android.adservices.service.common.ValidatorTestUtil;
+import com.android.adservices.service.common.ValidatorUtil;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.List;
+
+public class CustomAudienceValidatorTest {
+    private static final String ANOTHER_BUYER = "b.com";
+
+    private final CustomAudienceValidator mValidator =
+            new CustomAudienceValidator(
+                    CommonFixture.FIXED_CLOCK_TRUNCATED_TO_MILLI, FlagsFactory.getFlagsForTest());
+
+    @Test
+    public void testValidCustomAudience() {
+        Assert.assertTrue(
+                mValidator
+                        .getValidationViolations(
+                                CustomAudienceFixture.getValidBuilderForBuyer(
+                                                CommonFixture.VALID_BUYER)
+                                        .build())
+                        .isEmpty());
+    }
+
+    @Test
+    public void testValidCustomAudienceWithNullFields() {
+        Assert.assertTrue(
+                mValidator
+                        .getValidationViolations(
+                                CustomAudienceFixture.getValidBuilderForBuyer(
+                                                CommonFixture.VALID_BUYER)
+                                        .setTrustedBiddingData(null)
+                                        .setUserBiddingSignals(null)
+                                        .build())
+                        .isEmpty());
+    }
+
+    @Test
+    public void testInvalidBuyer() {
+        String buyerWithPath = CommonFixture.VALID_BUYER + "/path";
+        List<AdData> adDataList = AdDataFixture.getValidAdsByBuyer(buyerWithPath);
+
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mValidator.getValidationViolations(
+                        CustomAudienceFixture.getValidBuilderForBuyer(buyerWithPath).build()),
+                String.format(
+                        AdTechIdentifierValidator.IDENTIFIER_IS_AN_INVALID_DOMAIN_NAME,
+                        CustomAudienceValidator.CUSTOM_AUDIENCE_CLASS_NAME,
+                        ValidatorUtil.AD_TECH_ROLE_BUYER),
+                String.format(
+                        AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+                        ValidatorUtil.AD_TECH_ROLE_BUYER,
+                        buyerWithPath,
+                        ValidatorUtil.AD_TECH_ROLE_BUYER,
+                        CustomAudienceValidator.DAILY_UPDATE_URI_FIELD_NAME,
+                        CommonFixture.VALID_BUYER),
+                String.format(
+                        AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+                        ValidatorUtil.AD_TECH_ROLE_BUYER,
+                        buyerWithPath,
+                        ValidatorUtil.AD_TECH_ROLE_BUYER,
+                        CustomAudienceValidator.BIDDING_LOGIC_URI_FIELD_NAME,
+                        CommonFixture.VALID_BUYER),
+                String.format(
+                        AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+                        ValidatorUtil.AD_TECH_ROLE_BUYER,
+                        buyerWithPath,
+                        ValidatorUtil.AD_TECH_ROLE_BUYER,
+                        TrustedBiddingDataValidator.TRUSTED_BIDDING_URI_FIELD_NAME,
+                        CommonFixture.VALID_BUYER),
+                String.format(
+                        AdDataValidator.VIOLATION_FORMAT,
+                        adDataList.get(0),
+                        String.format(
+                                AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+                                ValidatorUtil.AD_TECH_ROLE_BUYER,
+                                buyerWithPath,
+                                ValidatorUtil.AD_TECH_ROLE_BUYER,
+                                AdDataValidator.RENDER_URI_FIELD_NAME,
+                                CommonFixture.VALID_BUYER)),
+                String.format(
+                        AdDataValidator.VIOLATION_FORMAT,
+                        adDataList.get(1),
+                        String.format(
+                                AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+                                ValidatorUtil.AD_TECH_ROLE_BUYER,
+                                buyerWithPath,
+                                ValidatorUtil.AD_TECH_ROLE_BUYER,
+                                AdDataValidator.RENDER_URI_FIELD_NAME,
+                                CommonFixture.VALID_BUYER)),
+                String.format(
+                        AdDataValidator.VIOLATION_FORMAT,
+                        adDataList.get(2),
+                        String.format(
+                                AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+                                ValidatorUtil.AD_TECH_ROLE_BUYER,
+                                buyerWithPath,
+                                ValidatorUtil.AD_TECH_ROLE_BUYER,
+                                AdDataValidator.RENDER_URI_FIELD_NAME,
+                                CommonFixture.VALID_BUYER)),
+                String.format(
+                        AdDataValidator.VIOLATION_FORMAT,
+                        adDataList.get(3),
+                        String.format(
+                                AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+                                ValidatorUtil.AD_TECH_ROLE_BUYER,
+                                buyerWithPath,
+                                ValidatorUtil.AD_TECH_ROLE_BUYER,
+                                AdDataValidator.RENDER_URI_FIELD_NAME,
+                                CommonFixture.VALID_BUYER)));
+    }
+
+    @Test
+    public void testInvalidDailyUpdateUriAndBiddingLogicUri() {
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mValidator.getValidationViolations(
+                        CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
+                                .setDailyUpdateUrl(
+                                        CustomAudienceFixture.getValidDailyUpdateUriByBuyer(
+                                                ANOTHER_BUYER))
+                                .setBiddingLogicUrl(
+                                        CustomAudienceFixture.getValidBiddingLogicUrlByBuyer(
+                                                ANOTHER_BUYER))
+                                .build()),
+                String.format(
+                        AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+                        ValidatorUtil.AD_TECH_ROLE_BUYER,
+                        CommonFixture.VALID_BUYER,
+                        ValidatorUtil.AD_TECH_ROLE_BUYER,
+                        CustomAudienceValidator.DAILY_UPDATE_URI_FIELD_NAME,
+                        ANOTHER_BUYER),
+                String.format(
+                        AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+                        ValidatorUtil.AD_TECH_ROLE_BUYER,
+                        CommonFixture.VALID_BUYER,
+                        ValidatorUtil.AD_TECH_ROLE_BUYER,
+                        CustomAudienceValidator.BIDDING_LOGIC_URI_FIELD_NAME,
+                        ANOTHER_BUYER));
+    }
+
+    @Test
+    public void testInvalidTrustedBiddingData() {
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mValidator.getValidationViolations(
+                        CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
+                                .setTrustedBiddingData(
+                                        TrustedBiddingDataFixture.getValidTrustedBiddingDataByBuyer(
+                                                ANOTHER_BUYER))
+                                .build()),
+                String.format(
+                        AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+                        ValidatorUtil.AD_TECH_ROLE_BUYER,
+                        CommonFixture.VALID_BUYER,
+                        ValidatorUtil.AD_TECH_ROLE_BUYER,
+                        TrustedBiddingDataValidator.TRUSTED_BIDDING_URI_FIELD_NAME,
+                        ANOTHER_BUYER));
+    }
+
+    @Test
+    public void testInvalidUserBiddingSignals() {
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mValidator.getValidationViolations(
+                        CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
+                                .setUserBiddingSignals("Not[A]VALID[JSON]")
+                                .build()),
+                String.format(
+                        JsonValidator.SHOULD_BE_A_VALID_JSON,
+                        CustomAudienceValidator.CUSTOM_AUDIENCE_CLASS_NAME,
+                        CustomAudienceValidator.USER_BIDDING_SIGNALS_FIELD_NAME));
+    }
+
+    @Test
+    public void testActivationTimeBeyondMaxAndExpireBeforeActivation() {
+
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mValidator.getValidationViolations(
+                        CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
+                                .setActivationTime(
+                                        CustomAudienceFixture.INVALID_DELAYED_ACTIVATION_TIME)
+                                .build()),
+                String.format(
+                        CustomAudienceTimestampValidator.VIOLATION_ACTIVATE_AFTER_MAX_ACTIVATE,
+                        CustomAudienceFixture.CUSTOM_AUDIENCE_MAX_ACTIVATION_DELAY_IN,
+                        CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI,
+                        CustomAudienceFixture.INVALID_DELAYED_ACTIVATION_TIME),
+                String.format(
+                        CustomAudienceTimestampValidator.VIOLATION_EXPIRE_BEFORE_ACTIVATION,
+                        CustomAudienceFixture.INVALID_DELAYED_ACTIVATION_TIME,
+                        CustomAudienceFixture.VALID_EXPIRATION_TIME));
+    }
+
+    @Test
+    public void testExpirationTimeBeforeNow() {
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mValidator.getValidationViolations(
+                        CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
+                                .setExpirationTime(
+                                        CustomAudienceFixture.INVALID_BEFORE_NOW_EXPIRATION_TIME)
+                                .build()),
+                String.format(
+                        CustomAudienceTimestampValidator.VIOLATION_EXPIRE_BEFORE_CURRENT_TIME,
+                        CustomAudienceFixture.INVALID_BEFORE_NOW_EXPIRATION_TIME));
+    }
+
+    @Test
+    public void testExpirationTimeBeforeActivation() {
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mValidator.getValidationViolations(
+                        CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
+                                .setExpirationTime(
+                                        CustomAudienceFixture
+                                                .INVALID_BEFORE_DELAYED_EXPIRATION_TIME)
+                                .setActivationTime(
+                                        CustomAudienceFixture.VALID_DELAYED_ACTIVATION_TIME)
+                                .build()),
+                String.format(
+                        CustomAudienceTimestampValidator.VIOLATION_EXPIRE_BEFORE_ACTIVATION,
+                        CustomAudienceFixture.VALID_DELAYED_ACTIVATION_TIME,
+                        CustomAudienceFixture.INVALID_BEFORE_DELAYED_EXPIRATION_TIME));
+    }
+
+    @Test
+    public void testInvalidAdData() {
+        AdData invalidAdDataWithAnotherBuyer =
+                new AdData.Builder()
+                        .setRenderUri(AdDataFixture.getValidRenderUrlByBuyer(ANOTHER_BUYER, 1))
+                        .setMetadata("{\"a\":1}")
+                        .build();
+        AdData invalidAdDataWithInvalidMetadata =
+                new AdData.Builder()
+                        .setRenderUri(
+                                AdDataFixture.getValidRenderUrlByBuyer(
+                                        CommonFixture.VALID_BUYER, 2))
+                        .setMetadata("not[valid]json")
+                        .build();
+        AdData validAdData =
+                new AdData.Builder()
+                        .setRenderUri(
+                                AdDataFixture.getValidRenderUrlByBuyer(
+                                        CommonFixture.VALID_BUYER, 3))
+                        .setMetadata("{\"a\":1}")
+                        .build();
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mValidator.getValidationViolations(
+                        CustomAudienceFixture.getValidBuilderForBuyer(CommonFixture.VALID_BUYER)
+                                .setAds(
+                                        List.of(
+                                                invalidAdDataWithAnotherBuyer,
+                                                invalidAdDataWithInvalidMetadata,
+                                                validAdData))
+                                .build()),
+                String.format(
+                        AdDataValidator.VIOLATION_FORMAT,
+                        invalidAdDataWithAnotherBuyer,
+                        String.format(
+                                AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+                                ValidatorUtil.AD_TECH_ROLE_BUYER,
+                                CommonFixture.VALID_BUYER,
+                                ValidatorUtil.AD_TECH_ROLE_BUYER,
+                                AdDataValidator.RENDER_URI_FIELD_NAME,
+                                ANOTHER_BUYER)),
+                String.format(
+                        AdDataValidator.VIOLATION_FORMAT,
+                        invalidAdDataWithInvalidMetadata,
+                        String.format(
+                                JsonValidator.SHOULD_BE_A_VALID_JSON,
+                                AdDataValidator.AD_DATA_CLASS_NAME,
+                                AdDataValidator.METADATA_FIELD_NAME)));
+    }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/TrustedBiddingDataValidatorTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/TrustedBiddingDataValidatorTest.java
new file mode 100644
index 0000000..bb8b1d3
--- /dev/null
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/TrustedBiddingDataValidatorTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 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.adservices.service.customaudience;
+
+import android.adservices.common.CommonFixture;
+import android.adservices.customaudience.TrustedBiddingDataFixture;
+
+import com.android.adservices.service.common.AdTechUriValidator;
+import com.android.adservices.service.common.ValidatorTestUtil;
+import com.android.adservices.service.common.ValidatorUtil;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TrustedBiddingDataValidatorTest {
+    private final TrustedBiddingDataValidator mValidator =
+            new TrustedBiddingDataValidator(CommonFixture.VALID_BUYER);
+
+    @Test
+    public void testValidTrustedBiddingData() {
+        Assert.assertTrue(
+                mValidator
+                        .getValidationViolations(
+                                TrustedBiddingDataFixture.getValidTrustedBiddingDataByBuyer(
+                                        CommonFixture.VALID_BUYER))
+                        .isEmpty());
+    }
+
+    @Test
+    public void testInvalidUri() {
+        String buyer = "b.com";
+        ValidatorTestUtil.assertViolationContaiinsOnly(
+                mValidator.getValidationViolations(
+                        TrustedBiddingDataFixture.getValidTrustedBiddingDataByBuyer(buyer)),
+                String.format(
+                        AdTechUriValidator.IDENTIFIER_AND_URL_ARE_INCONSISTENT,
+                        ValidatorUtil.AD_TECH_ROLE_BUYER,
+                        CommonFixture.VALID_BUYER,
+                        ValidatorUtil.AD_TECH_ROLE_BUYER,
+                        TrustedBiddingDataValidator.TRUSTED_BIDDING_URI_FIELD_NAME,
+                        buyer));
+    }
+}