[fetchCA] Handle request filter failures
Test: atest AdServicesServiceCoreUnitTests
Bug: 282017511
Fixes: 286671197
Change-Id: I69c438625a5b93d6653fadede87fec3e619b5519
diff --git a/adservices/service-core/java/com/android/adservices/service/common/AbstractFledgeServiceFilter.java b/adservices/service-core/java/com/android/adservices/service/common/AbstractFledgeServiceFilter.java
index 677f76b..e3135c2 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/AbstractFledgeServiceFilter.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/AbstractFledgeServiceFilter.java
@@ -94,6 +94,20 @@
}
/**
+ * Asserts caller has user consent to use FLEDGE APIs in the calling app and persists consent
+ * for.
+ *
+ * @throws ConsentManager.RevokedConsentException if FLEDGE or the Privacy Sandbox do not have
+ * user consent
+ */
+ protected void assertAndPersistCallerHasUserConsentForApp(String callerPackageName)
+ throws ConsentManager.RevokedConsentException {
+ if (mConsentManager.isFledgeConsentRevokedForAppAfterSettingFledgeUse(callerPackageName)) {
+ throw new ConsentManager.RevokedConsentException();
+ }
+ }
+
+ /**
* Asserts that the caller has the appropriate foreground status.
*
* @throws AppImportanceFilter.WrongCallingApplicationStateException if the foreground check
diff --git a/adservices/service-core/java/com/android/adservices/service/common/CustomAudienceServiceFilter.java b/adservices/service-core/java/com/android/adservices/service/common/CustomAudienceServiceFilter.java
index 5b7f924..c63d45c 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/CustomAudienceServiceFilter.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/CustomAudienceServiceFilter.java
@@ -63,7 +63,7 @@
* skipped.
* @param callerPackageName caller package name to be validated
* @param enforceForeground whether to enforce a foreground check
- * @param enforceConsent currently unused in CustomAudienceServiceFilter
+ * @param enforceConsent whether to enforce per-app consent
* @param callerUid caller's uid from the Binder thread
* @param apiName the id of the api being called
* @param apiKey api-specific throttler key
@@ -106,5 +106,10 @@
sLogger.v("Validating caller package is in allow list.");
assertAppInAllowList(callerPackageName, apiName);
+
+ if (enforceConsent) {
+ sLogger.v("Validating per-app user consent.");
+ assertAndPersistCallerHasUserConsentForApp(callerPackageName);
+ }
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/customaudience/FetchCustomAudienceImpl.java b/adservices/service-core/java/com/android/adservices/service/customaudience/FetchCustomAudienceImpl.java
index c126739..95e1792 100644
--- a/adservices/service-core/java/com/android/adservices/service/customaudience/FetchCustomAudienceImpl.java
+++ b/adservices/service-core/java/com/android/adservices/service/customaudience/FetchCustomAudienceImpl.java
@@ -27,7 +27,6 @@
import android.annotation.NonNull;
import android.net.Uri;
import android.os.Build;
-import android.os.LimitExceededException;
import android.os.RemoteException;
import androidx.annotation.RequiresApi;
@@ -37,15 +36,13 @@
import com.android.adservices.data.customaudience.DBCustomAudience;
import com.android.adservices.service.Flags;
import com.android.adservices.service.common.AdTechIdentifierValidator;
-import com.android.adservices.service.common.AppImportanceFilter;
import com.android.adservices.service.common.CallingAppUidSupplier;
import com.android.adservices.service.common.CustomAudienceServiceFilter;
-import com.android.adservices.service.common.FledgeAllowListsFilter;
-import com.android.adservices.service.common.FledgeAuthorizationFilter;
import com.android.adservices.service.common.httpclient.AdServicesHttpClientRequest;
import com.android.adservices.service.common.httpclient.AdServicesHttpClientResponse;
import com.android.adservices.service.common.httpclient.AdServicesHttpsClient;
import com.android.adservices.service.consent.ConsentManager;
+import com.android.adservices.service.exception.FilterException;
import com.android.adservices.service.stats.AdServicesLogger;
import com.android.internal.annotations.VisibleForTesting;
@@ -163,7 +160,16 @@
t,
"Error encountered in fetchCustomAudience"
+ " execution");
- if (t instanceof ConsentManager.RevokedConsentException) {
+ if (t instanceof FilterException
+ && t.getCause()
+ instanceof
+ ConsentManager.RevokedConsentException) {
+ // Skip logging if a FilterException occurs.
+ // AdSelectionServiceFilter ensures the failing
+ // assertion is logged
+ // internally.
+
+ // Fail Silently by notifying success to caller
notifySuccess(callback);
} else {
notifyFailure(callback, t);
@@ -189,19 +195,17 @@
mBuyer = AdTechIdentifier.fromString(host);
// Filter request
- mCustomAudienceServiceFilter.filterRequest(
- mBuyer,
- input.getCallerPackageName(),
- mEnforceForegroundStatusForFledgeCustomAudience,
- false,
- mCallingAppUidSupplier.getCallingAppUid(),
- API_NAME,
- FLEDGE_API_FETCH_CUSTOM_AUDIENCE);
-
- if (mConsentManager.isFledgeConsentRevokedForAppAfterSettingFledgeUse(
- input.getCallerPackageName())) {
- sLogger.v("Consent revoked");
- throw new ConsentManager.RevokedConsentException();
+ try {
+ mCustomAudienceServiceFilter.filterRequest(
+ mBuyer,
+ input.getCallerPackageName(),
+ mEnforceForegroundStatusForFledgeCustomAudience,
+ true,
+ mCallingAppUidSupplier.getCallingAppUid(),
+ API_NAME,
+ FLEDGE_API_FETCH_CUSTOM_AUDIENCE);
+ } catch (Throwable t) {
+ throw new FilterException(t);
}
// Validate request
@@ -337,35 +341,33 @@
// TODO(b/283857101): Move DB handling to persistResponse using a common CustomAudienceReader.
// private Void persistResponse (@NonNull DBCustomAudience customAudience) {}
- private void notifyFailure(FetchAndJoinCustomAudienceCallback callback, Throwable exception) {
+ private void notifyFailure(FetchAndJoinCustomAudienceCallback callback, Throwable t) {
try {
- int resultCode = AdServicesStatusUtils.STATUS_UNSET;
+ int resultCode;
- if (exception instanceof NullPointerException
- || exception instanceof IllegalArgumentException) {
+ boolean isFilterException = t instanceof FilterException;
+
+ if (isFilterException) {
+ resultCode = FilterException.getResultCode(t);
+ } else if (t instanceof IllegalArgumentException) {
resultCode = AdServicesStatusUtils.STATUS_INVALID_ARGUMENT;
- } else if (exception
- instanceof AppImportanceFilter.WrongCallingApplicationStateException) {
- resultCode = AdServicesStatusUtils.STATUS_BACKGROUND_CALLER;
- } else if (exception instanceof FledgeAuthorizationFilter.CallerMismatchException) {
- resultCode = AdServicesStatusUtils.STATUS_UNAUTHORIZED;
- } else if (exception instanceof FledgeAuthorizationFilter.AdTechNotAllowedException
- || exception instanceof FledgeAllowListsFilter.AppNotAllowedException) {
- resultCode = AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED;
- } else if (exception instanceof LimitExceededException) {
- resultCode = AdServicesStatusUtils.STATUS_RATE_LIMIT_REACHED;
- } else if (exception instanceof IllegalStateException) {
- resultCode = AdServicesStatusUtils.STATUS_INTERNAL_ERROR;
} else {
- sLogger.e(exception, "Unexpected error during operation");
+ sLogger.d(t, "Unexpected error during operation");
resultCode = AdServicesStatusUtils.STATUS_INTERNAL_ERROR;
}
+
+ // Skip logging if a FilterException occurs.
+ // AdSelectionServiceFilter ensures the failing assertion is logged internally.
+ // Note: Failure is logged before the callback to ensure deterministic testing.
+ if (!isFilterException) {
+ mAdServicesLogger.logFledgeApiCallStats(API_NAME, resultCode, 0);
+ }
+
callback.onFailure(
new FledgeErrorResponse.Builder()
.setStatusCode(resultCode)
- .setErrorMessage(exception.getMessage())
+ .setErrorMessage(t.getMessage())
.build());
- mAdServicesLogger.logFledgeApiCallStats(API_NAME, resultCode, 0);
} catch (RemoteException e) {
sLogger.e(e, "Unable to send failed result to the callback");
mAdServicesLogger.logFledgeApiCallStats(
@@ -377,9 +379,9 @@
/** Invokes the onSuccess function from the callback and handles the exception. */
private void notifySuccess(@NonNull FetchAndJoinCustomAudienceCallback callback) {
try {
- callback.onSuccess();
mAdServicesLogger.logFledgeApiCallStats(
API_NAME, AdServicesStatusUtils.STATUS_SUCCESS, 0);
+ callback.onSuccess();
} catch (RemoteException e) {
sLogger.e(e, "Unable to send successful result to the callback");
mAdServicesLogger.logFledgeApiCallStats(
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/CustomAudienceServiceFilterTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/CustomAudienceServiceFilterTest.java
index 971346e..f302b77 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/CustomAudienceServiceFilterTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/common/CustomAudienceServiceFilterTest.java
@@ -16,16 +16,17 @@
package com.android.adservices.service.common;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertThrows;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
import android.adservices.common.AdTechIdentifier;
import android.adservices.common.CommonFixture;
@@ -260,4 +261,53 @@
API_NAME,
Throttler.ApiKey.UNKNOWN));
}
+
+ @Test
+ public void testAssertAndPersistCallerHasUserConsentForApp_enforceConsentTrue_succeeds() {
+ doReturn(false)
+ .when(mConsentManagerMock)
+ .isFledgeConsentRevokedForAppAfterSettingFledgeUse(CALLER_PACKAGE_NAME);
+ mCustomAudienceServiceFilter.filterRequest(
+ SELLER_VALID,
+ CALLER_PACKAGE_NAME,
+ false,
+ true,
+ MY_UID,
+ API_NAME,
+ Throttler.ApiKey.UNKNOWN);
+ }
+
+ @Test
+ public void testAssertAndPersistCallerHasUserConsentForApp_enforceConsentTrue_throws() {
+ doReturn(true)
+ .when(mConsentManagerMock)
+ .isFledgeConsentRevokedForAppAfterSettingFledgeUse(CALLER_PACKAGE_NAME);
+
+ assertThrows(
+ ConsentManager.RevokedConsentException.class,
+ () ->
+ mCustomAudienceServiceFilter.filterRequest(
+ SELLER_VALID,
+ CALLER_PACKAGE_NAME,
+ false,
+ true,
+ MY_UID,
+ API_NAME,
+ Throttler.ApiKey.UNKNOWN));
+ }
+
+ @Test
+ public void testAssertAndPersistCallerHasUserConsentForApp_enforceConsentFalse_succeeds() {
+ doReturn(true)
+ .when(mConsentManagerMock)
+ .isFledgeConsentRevokedForAppAfterSettingFledgeUse(CALLER_PACKAGE_NAME);
+ mCustomAudienceServiceFilter.filterRequest(
+ SELLER_VALID,
+ CALLER_PACKAGE_NAME,
+ false,
+ false,
+ MY_UID,
+ API_NAME,
+ Throttler.ApiKey.UNKNOWN);
+ }
}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/FetchCustomAudienceImplTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/FetchCustomAudienceImplTest.java
index 45734f5..c0c899c 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/FetchCustomAudienceImplTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/customaudience/FetchCustomAudienceImplTest.java
@@ -16,12 +16,23 @@
package com.android.adservices.service.customaudience;
+import static android.adservices.common.AdServicesStatusUtils.ILLEGAL_STATE_BACKGROUND_CALLER_ERROR_MESSAGE;
+import static android.adservices.common.AdServicesStatusUtils.RATE_LIMIT_REACHED_ERROR_MESSAGE;
+import static android.adservices.common.AdServicesStatusUtils.SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ERROR_MESSAGE;
+import static android.adservices.common.AdServicesStatusUtils.SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ON_BEHALF_ERROR_MESSAGE;
+import static android.adservices.common.AdServicesStatusUtils.STATUS_BACKGROUND_CALLER;
+import static android.adservices.common.AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED;
import static android.adservices.common.AdServicesStatusUtils.STATUS_INTERNAL_ERROR;
+import static android.adservices.common.AdServicesStatusUtils.STATUS_RATE_LIMIT_REACHED;
import static android.adservices.common.AdServicesStatusUtils.STATUS_SUCCESS;
+import static android.adservices.common.AdServicesStatusUtils.STATUS_UNAUTHORIZED;
+import static android.adservices.common.AdServicesStatusUtils.STATUS_USER_CONSENT_REVOKED;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__API_NAME_UNKNOWN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
@@ -37,6 +48,8 @@
import android.adservices.customaudience.FetchAndJoinCustomAudienceInput;
import android.adservices.http.MockWebServerRule;
import android.net.Uri;
+import android.os.LimitExceededException;
+import android.os.Process;
import android.os.RemoteException;
import com.android.adservices.LogUtil;
@@ -44,7 +57,11 @@
import com.android.adservices.concurrency.AdServicesExecutors;
import com.android.adservices.data.customaudience.CustomAudienceDao;
import com.android.adservices.service.Flags;
+import com.android.adservices.service.common.AppImportanceFilter;
import com.android.adservices.service.common.CustomAudienceServiceFilter;
+import com.android.adservices.service.common.FledgeAllowListsFilter;
+import com.android.adservices.service.common.FledgeAuthorizationFilter;
+import com.android.adservices.service.common.Throttler;
import com.android.adservices.service.common.cache.CacheProviderFactory;
import com.android.adservices.service.common.httpclient.AdServicesHttpsClient;
import com.android.adservices.service.consent.ConsentManager;
@@ -81,17 +98,16 @@
@Mock private ConsentManager mConsentManagerMock;
@Mock private CustomAudienceServiceFilter mCustomAudienceServiceFilterMock;
@Mock private CustomAudienceDao mCustomAudienceDaoMock;
-
@Spy
private final AdServicesHttpsClient mHttpClientSpy =
new AdServicesHttpsClient(
AdServicesExecutors.getBlockingExecutor(),
CacheProviderFactory.createNoOpCache());
-
@Rule public MockWebServerRule mMockWebServerRule = MockWebServerRuleFactory.createForHttps();
+ private static final AdTechIdentifier BUYER = AdTechIdentifier.fromString("localhost");
private Uri mFetchUri;
private FetchCustomAudienceImpl mFetchCustomAudienceImpl;
- private FetchAndJoinCustomAudienceInput mValidInput;
+ private FetchAndJoinCustomAudienceInput.Builder mInputBuilder;
private MockitoSession mStaticMockSession = null;
@Before
@@ -105,14 +121,13 @@
mFetchUri = mMockWebServerRule.uriForPath("/fetch");
- mValidInput =
+ mInputBuilder =
new FetchAndJoinCustomAudienceInput.Builder(
mFetchUri, CustomAudienceFixture.VALID_OWNER)
.setName(CustomAudienceFixture.VALID_NAME)
.setActivationTime(CustomAudienceFixture.VALID_ACTIVATION_TIME)
.setExpirationTime(CustomAudienceFixture.VALID_EXPIRATION_TIME)
- .setUserBiddingSignals(CustomAudienceFixture.VALID_USER_BIDDING_SIGNALS)
- .build();
+ .setUserBiddingSignals(CustomAudienceFixture.VALID_USER_BIDDING_SIGNALS);
mFetchCustomAudienceImpl =
new FetchCustomAudienceImpl(
@@ -156,8 +171,17 @@
mCustomAudienceServiceFilterMock,
mHttpClientSpy);
- FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mValidInput);
+ MockWebServer mockWebServer =
+ mMockWebServerRule.startMockWebServer(
+ List.of(
+ new MockResponse()
+ .setBody(
+ FetchCustomAudienceFixture
+ .getFullSuccessfulJsonResponseString())));
+ FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
+
+ assertEquals(0, mockWebServer.getRequestCount());
assertFalse(callback.mIsSuccess);
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
@@ -176,15 +200,14 @@
FetchCustomAudienceFixture
.getFullSuccessfulJsonResponseString())));
- FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mValidInput);
+ FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
assertEquals(1, mockWebServer.getRequestCount());
assertTrue(callback.mIsSuccess);
verify(mCustomAudienceDaoMock)
.insertOrOverwriteCustomAudience(
FetchCustomAudienceFixture.getFullSuccessfulDBCustomAudience(),
- CustomAudienceFixture.getValidDailyUpdateUriByBuyer(
- AdTechIdentifier.fromString("localhost")));
+ CustomAudienceFixture.getValidDailyUpdateUriByBuyer(BUYER));
verify(mAdServicesLoggerMock)
.logFledgeApiCallStats(
eq(AD_SERVICES_API_CALLED__API_NAME__API_NAME_UNKNOWN),
@@ -192,6 +215,185 @@
anyInt());
}
+ @Test
+ public void testFetchCustomAudience_throwsWithInvalidPackageName() throws Exception {
+ String otherPackageName = CustomAudienceFixture.VALID_OWNER + "incorrectPackage";
+
+ doThrow(new FledgeAuthorizationFilter.CallerMismatchException())
+ .when(mCustomAudienceServiceFilterMock)
+ .filterRequest(
+ BUYER,
+ otherPackageName,
+ true,
+ true,
+ Process.myUid(),
+ AD_SERVICES_API_CALLED__API_NAME__API_NAME_UNKNOWN,
+ Throttler.ApiKey.FLEDGE_API_FETCH_CUSTOM_AUDIENCE);
+
+ FetchCustomAudienceTestCallback callback =
+ callFetchCustomAudience(
+ mInputBuilder.setCallerPackageName(otherPackageName).build());
+
+ assertFalse(callback.mIsSuccess);
+ assertEquals(STATUS_UNAUTHORIZED, callback.mFledgeErrorResponse.getStatusCode());
+ assertEquals(
+ SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ON_BEHALF_ERROR_MESSAGE,
+ callback.mFledgeErrorResponse.getErrorMessage());
+
+ // Confirm a duplicate log entry does not exist.
+ // CustomAudienceServiceFilter ensures the failing assertion is logged internally.
+ verify(mAdServicesLoggerMock, never())
+ .logFledgeApiCallStats(
+ eq(AD_SERVICES_API_CALLED__API_NAME__API_NAME_UNKNOWN),
+ eq(STATUS_UNAUTHORIZED),
+ anyInt());
+ }
+
+ @Test
+ public void testFetchCustomAudience_throwsWhenThrottled() throws Exception {
+ doThrow(new LimitExceededException(RATE_LIMIT_REACHED_ERROR_MESSAGE))
+ .when(mCustomAudienceServiceFilterMock)
+ .filterRequest(
+ BUYER,
+ CustomAudienceFixture.VALID_OWNER,
+ true,
+ true,
+ Process.myUid(),
+ AD_SERVICES_API_CALLED__API_NAME__API_NAME_UNKNOWN,
+ Throttler.ApiKey.FLEDGE_API_FETCH_CUSTOM_AUDIENCE);
+
+ FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
+
+ assertFalse(callback.mIsSuccess);
+ assertEquals(STATUS_RATE_LIMIT_REACHED, callback.mFledgeErrorResponse.getStatusCode());
+ assertEquals(
+ RATE_LIMIT_REACHED_ERROR_MESSAGE, callback.mFledgeErrorResponse.getErrorMessage());
+
+ // Confirm a duplicate log entry does not exist.
+ // CustomAudienceServiceFilter ensures the failing assertion is logged internally.
+ verify(mAdServicesLoggerMock, never())
+ .logFledgeApiCallStats(
+ eq(AD_SERVICES_API_CALLED__API_NAME__API_NAME_UNKNOWN),
+ eq(STATUS_RATE_LIMIT_REACHED),
+ anyInt());
+ }
+
+ @Test
+ public void testFetchCustomAudience_throwsWithFailedForegroundCheck() throws Exception {
+ doThrow(new AppImportanceFilter.WrongCallingApplicationStateException())
+ .when(mCustomAudienceServiceFilterMock)
+ .filterRequest(
+ BUYER,
+ CustomAudienceFixture.VALID_OWNER,
+ true,
+ true,
+ Process.myUid(),
+ AD_SERVICES_API_CALLED__API_NAME__API_NAME_UNKNOWN,
+ Throttler.ApiKey.FLEDGE_API_FETCH_CUSTOM_AUDIENCE);
+
+ FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
+
+ assertFalse(callback.mIsSuccess);
+ assertEquals(STATUS_BACKGROUND_CALLER, callback.mFledgeErrorResponse.getStatusCode());
+ assertEquals(
+ ILLEGAL_STATE_BACKGROUND_CALLER_ERROR_MESSAGE,
+ callback.mFledgeErrorResponse.getErrorMessage());
+
+ // Confirm a duplicate log entry does not exist.
+ // CustomAudienceServiceFilter ensures the failing assertion is logged internally.
+ verify(mAdServicesLoggerMock, never())
+ .logFledgeApiCallStats(
+ eq(AD_SERVICES_API_CALLED__API_NAME__API_NAME_UNKNOWN),
+ eq(STATUS_BACKGROUND_CALLER),
+ anyInt());
+ }
+
+ @Test
+ public void testFetchCustomAudience_throwsWithFailedEnrollmentCheck() throws Exception {
+ doThrow(new FledgeAuthorizationFilter.AdTechNotAllowedException())
+ .when(mCustomAudienceServiceFilterMock)
+ .filterRequest(
+ BUYER,
+ CustomAudienceFixture.VALID_OWNER,
+ true,
+ true,
+ Process.myUid(),
+ AD_SERVICES_API_CALLED__API_NAME__API_NAME_UNKNOWN,
+ Throttler.ApiKey.FLEDGE_API_FETCH_CUSTOM_AUDIENCE);
+
+ FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
+
+ assertFalse(callback.mIsSuccess);
+ assertEquals(STATUS_CALLER_NOT_ALLOWED, callback.mFledgeErrorResponse.getStatusCode());
+ assertEquals(
+ SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ERROR_MESSAGE,
+ callback.mFledgeErrorResponse.getErrorMessage());
+
+ // Confirm a duplicate log entry does not exist.
+ // CustomAudienceServiceFilter ensures the failing assertion is logged internally.
+ verify(mAdServicesLoggerMock, never())
+ .logFledgeApiCallStats(
+ eq(AD_SERVICES_API_CALLED__API_NAME__API_NAME_UNKNOWN),
+ eq(STATUS_CALLER_NOT_ALLOWED),
+ anyInt());
+ }
+
+ @Test
+ public void testFetchCustomAudience_throwsWhenAppCannotUsePPAPI() throws Exception {
+ doThrow(new FledgeAllowListsFilter.AppNotAllowedException())
+ .when(mCustomAudienceServiceFilterMock)
+ .filterRequest(
+ BUYER,
+ CustomAudienceFixture.VALID_OWNER,
+ true,
+ true,
+ Process.myUid(),
+ AD_SERVICES_API_CALLED__API_NAME__API_NAME_UNKNOWN,
+ Throttler.ApiKey.FLEDGE_API_FETCH_CUSTOM_AUDIENCE);
+
+ FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
+
+ assertFalse(callback.mIsSuccess);
+ assertEquals(STATUS_CALLER_NOT_ALLOWED, callback.mFledgeErrorResponse.getStatusCode());
+ assertEquals(
+ SECURITY_EXCEPTION_CALLER_NOT_ALLOWED_ERROR_MESSAGE,
+ callback.mFledgeErrorResponse.getErrorMessage());
+
+ // Confirm a duplicate log entry does not exist.
+ // CustomAudienceServiceFilter ensures the failing assertion is logged internally.
+ verify(mAdServicesLoggerMock, never())
+ .logFledgeApiCallStats(
+ eq(AD_SERVICES_API_CALLED__API_NAME__API_NAME_UNKNOWN),
+ eq(STATUS_CALLER_NOT_ALLOWED),
+ anyInt());
+ }
+
+ @Test
+ public void testFetchCustomAudience_failsSilentlyWithRevokeConsent() throws Exception {
+ doThrow(new ConsentManager.RevokedConsentException())
+ .when(mCustomAudienceServiceFilterMock)
+ .filterRequest(
+ BUYER,
+ CustomAudienceFixture.VALID_OWNER,
+ true,
+ true,
+ Process.myUid(),
+ AD_SERVICES_API_CALLED__API_NAME__API_NAME_UNKNOWN,
+ Throttler.ApiKey.FLEDGE_API_FETCH_CUSTOM_AUDIENCE);
+
+ FetchCustomAudienceTestCallback callback = callFetchCustomAudience(mInputBuilder.build());
+
+ assertTrue(callback.mIsSuccess);
+
+ // Confirm a duplicate log entry does not exist.
+ // CustomAudienceServiceFilter ensures the failing assertion is logged internally.
+ verify(mAdServicesLoggerMock, never())
+ .logFledgeApiCallStats(
+ eq(AD_SERVICES_API_CALLED__API_NAME__API_NAME_UNKNOWN),
+ eq(STATUS_USER_CONSENT_REVOKED),
+ anyInt());
+ }
+
private FetchCustomAudienceTestCallback callFetchCustomAudience(
FetchAndJoinCustomAudienceInput input) throws Exception {
CountDownLatch resultLatch = new CountDownLatch(1);