1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.adservices.service.adselection; 18 19 import static android.adservices.common.AdServicesStatusUtils.STATUS_INTERNAL_ERROR; 20 import static android.adservices.common.AdServicesStatusUtils.STATUS_INVALID_ARGUMENT; 21 import static android.adservices.common.AdServicesStatusUtils.STATUS_SUCCESS; 22 import static android.adservices.common.AdServicesStatusUtils.STATUS_UNKNOWN_ERROR; 23 import static android.adservices.common.AdServicesStatusUtils.STATUS_USER_CONSENT_REVOKED; 24 import static android.adservices.common.CommonFixture.TEST_PACKAGE_NAME; 25 26 import static com.google.common.truth.Truth.assertThat; 27 import static com.google.common.truth.Truth.assertWithMessage; 28 29 import static org.mockito.ArgumentMatchers.any; 30 import static org.mockito.ArgumentMatchers.anyBoolean; 31 import static org.mockito.ArgumentMatchers.anyInt; 32 import static org.mockito.ArgumentMatchers.anyLong; 33 import static org.mockito.ArgumentMatchers.eq; 34 import static org.mockito.Mockito.doAnswer; 35 import static org.mockito.Mockito.doReturn; 36 import static org.mockito.Mockito.doThrow; 37 import static org.mockito.Mockito.never; 38 import static org.mockito.Mockito.verify; 39 import static org.mockito.Mockito.verifyNoMoreInteractions; 40 41 import android.adservices.adselection.UpdateAdCounterHistogramCallback; 42 import android.adservices.adselection.UpdateAdCounterHistogramInput; 43 import android.adservices.common.CommonFixture; 44 import android.adservices.common.FledgeErrorResponse; 45 import android.adservices.common.FrequencyCapFilters; 46 import android.os.Parcel; 47 import android.os.RemoteException; 48 49 import com.android.adservices.service.Flags; 50 import com.android.adservices.service.common.AdSelectionServiceFilter; 51 import com.android.adservices.service.common.FledgeAuthorizationFilter; 52 import com.android.adservices.service.consent.ConsentManager; 53 import com.android.adservices.service.devapi.DevContext; 54 import com.android.adservices.service.exception.FilterException; 55 import com.android.adservices.service.stats.AdServicesLogger; 56 import com.android.adservices.service.stats.AdServicesStatsLog; 57 import com.android.adservices.shared.testing.SdkLevelSupportRule; 58 59 import com.google.common.util.concurrent.MoreExecutors; 60 61 import org.junit.Before; 62 import org.junit.Rule; 63 import org.junit.Test; 64 import org.mockito.Mock; 65 import org.mockito.junit.MockitoJUnit; 66 import org.mockito.junit.MockitoRule; 67 68 import java.util.concurrent.CountDownLatch; 69 import java.util.concurrent.ExecutorService; 70 import java.util.concurrent.TimeUnit; 71 72 public class UpdateAdCounterHistogramWorkerTest { 73 private static final int CALLBACK_WAIT_MS = 500; 74 private static final int CALLER_UID = 10; 75 private static final long AD_SELECTION_ID = 20; 76 private static final int LOGGING_API_NAME = 77 AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__UPDATE_AD_COUNTER_HISTOGRAM; 78 private static final ExecutorService DIRECT_EXECUTOR = MoreExecutors.newDirectExecutorService(); 79 80 @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); 81 82 @Mock private AdCounterHistogramUpdater mHistogramUpdaterMock; 83 @Mock private AdServicesLogger mAdServicesLoggerMock; 84 @Mock private AdSelectionServiceFilter mServiceFilterMock; 85 @Mock private ConsentManager mConsentManagerMock; 86 87 private UpdateAdCounterHistogramWorker mUpdateWorker; 88 private UpdateAdCounterHistogramInput mInputParams; 89 90 @Rule(order = 0) 91 public final SdkLevelSupportRule sdkLevel = SdkLevelSupportRule.forAtLeastS(); 92 93 @Before setup()94 public void setup() { 95 mUpdateWorker = 96 new UpdateAdCounterHistogramWorker( 97 mHistogramUpdaterMock, 98 DIRECT_EXECUTOR, 99 CommonFixture.FIXED_CLOCK_TRUNCATED_TO_MILLI, 100 mAdServicesLoggerMock, 101 new FlagsOverridingAdFiltering(true), 102 mServiceFilterMock, 103 mConsentManagerMock, 104 CALLER_UID, 105 DevContext.createForDevOptionsDisabled()); 106 107 mInputParams = 108 new UpdateAdCounterHistogramInput.Builder( 109 AD_SELECTION_ID, 110 FrequencyCapFilters.AD_EVENT_TYPE_CLICK, 111 CommonFixture.VALID_BUYER_1, 112 TEST_PACKAGE_NAME) 113 .build(); 114 } 115 116 @Test testWorkerUpdatesHistogramAndNotifiesSuccess()117 public void testWorkerUpdatesHistogramAndNotifiesSuccess() throws InterruptedException { 118 CountDownLatch callbackLatch = new CountDownLatch(1); 119 UpdateAdCounterHistogramTestCallback callback = 120 new UpdateAdCounterHistogramTestCallback(callbackLatch); 121 122 mUpdateWorker.updateAdCounterHistogram(mInputParams, callback); 123 124 assertThat(callbackLatch.await(CALLBACK_WAIT_MS, TimeUnit.MILLISECONDS)).isTrue(); 125 assertThat(callback.mIsSuccess).isTrue(); 126 127 verify(mHistogramUpdaterMock) 128 .updateNonWinHistogram( 129 eq(AD_SELECTION_ID), 130 eq(TEST_PACKAGE_NAME), 131 eq(FrequencyCapFilters.AD_EVENT_TYPE_CLICK), 132 eq(CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI)); 133 134 verify(mAdServicesLoggerMock) 135 .logFledgeApiCallStats( 136 eq(LOGGING_API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_SUCCESS), anyInt()); 137 } 138 139 @Test testWorkerValidatesInvalidAdEventTypeAndNotifiesFailure()140 public void testWorkerValidatesInvalidAdEventTypeAndNotifiesFailure() throws Exception { 141 Parcel sourceParcel = Parcel.obtain(); 142 sourceParcel.writeLong(AD_SELECTION_ID); 143 sourceParcel.writeInt(FrequencyCapFilters.AD_EVENT_TYPE_MAX + 10); 144 CommonFixture.VALID_BUYER_1.writeToParcel(sourceParcel, 0); 145 sourceParcel.writeString(TEST_PACKAGE_NAME); 146 sourceParcel.setDataPosition(0); 147 148 UpdateAdCounterHistogramInput invalidInputParams = 149 UpdateAdCounterHistogramInput.CREATOR.createFromParcel(sourceParcel); 150 151 CountDownLatch callbackLatch = new CountDownLatch(1); 152 UpdateAdCounterHistogramTestCallback callback = 153 new UpdateAdCounterHistogramTestCallback(callbackLatch); 154 155 mUpdateWorker.updateAdCounterHistogram(invalidInputParams, callback); 156 157 assertWithMessage("Callback latch wait") 158 .that(callbackLatch.await(CALLBACK_WAIT_MS, TimeUnit.MILLISECONDS)) 159 .isTrue(); 160 assertWithMessage("Callback success").that(callback.mIsSuccess).isFalse(); 161 162 verify(mHistogramUpdaterMock, never()) 163 .updateNonWinHistogram(anyLong(), any(), anyInt(), any()); 164 165 verify(mAdServicesLoggerMock) 166 .logFledgeApiCallStats( 167 eq(LOGGING_API_NAME), 168 eq(TEST_PACKAGE_NAME), 169 eq(STATUS_INVALID_ARGUMENT), 170 anyInt()); 171 } 172 173 @Test testWorkerFeatureFlagDisabledStopsAndNotifiesFailure()174 public void testWorkerFeatureFlagDisabledStopsAndNotifiesFailure() throws InterruptedException { 175 mUpdateWorker = 176 new UpdateAdCounterHistogramWorker( 177 mHistogramUpdaterMock, 178 DIRECT_EXECUTOR, 179 CommonFixture.FIXED_CLOCK_TRUNCATED_TO_MILLI, 180 mAdServicesLoggerMock, 181 new FlagsOverridingAdFiltering(false), 182 mServiceFilterMock, 183 mConsentManagerMock, 184 CALLER_UID, 185 DevContext.createForDevOptionsDisabled()); 186 187 CountDownLatch callbackLatch = new CountDownLatch(1); 188 UpdateAdCounterHistogramTestCallback callback = 189 new UpdateAdCounterHistogramTestCallback(callbackLatch); 190 191 mUpdateWorker.updateAdCounterHistogram(mInputParams, callback); 192 193 assertThat(callbackLatch.await(CALLBACK_WAIT_MS, TimeUnit.MILLISECONDS)).isTrue(); 194 assertThat(callback.mIsSuccess).isFalse(); 195 assertThat(callback.mFledgeErrorResponse.getStatusCode()).isEqualTo(STATUS_INTERNAL_ERROR); 196 197 verify(mAdServicesLoggerMock) 198 .logFledgeApiCallStats( 199 eq(LOGGING_API_NAME), 200 eq(TEST_PACKAGE_NAME), 201 eq(STATUS_INTERNAL_ERROR), 202 anyInt()); 203 204 verifyNoMoreInteractions(mHistogramUpdaterMock); 205 } 206 207 @Test testWorkerFilterFailureStopsAndNotifiesFailure()208 public void testWorkerFilterFailureStopsAndNotifiesFailure() throws InterruptedException { 209 doThrow(new FilterException(new FledgeAuthorizationFilter.CallerMismatchException())) 210 .when(mServiceFilterMock) 211 .filterRequest( 212 any(), any(), anyBoolean(), anyBoolean(), anyInt(), anyInt(), any(), any()); 213 214 CountDownLatch callbackLatch = new CountDownLatch(1); 215 UpdateAdCounterHistogramTestCallback callback = 216 new UpdateAdCounterHistogramTestCallback(callbackLatch); 217 218 mUpdateWorker.updateAdCounterHistogram(mInputParams, callback); 219 220 assertThat(callbackLatch.await(CALLBACK_WAIT_MS, TimeUnit.MILLISECONDS)).isTrue(); 221 assertThat(callback.mIsSuccess).isFalse(); 222 223 verifyNoMoreInteractions(mHistogramUpdaterMock, mAdServicesLoggerMock); 224 } 225 226 @Test testWorkerConsentFailureStopsAndNotifiesSuccess()227 public void testWorkerConsentFailureStopsAndNotifiesSuccess() throws InterruptedException { 228 doReturn(true) 229 .when(mConsentManagerMock) 230 .isFledgeConsentRevokedForAppAfterSettingFledgeUse(any()); 231 232 CountDownLatch callbackLatch = new CountDownLatch(1); 233 UpdateAdCounterHistogramTestCallback callback = 234 new UpdateAdCounterHistogramTestCallback(callbackLatch); 235 236 mUpdateWorker.updateAdCounterHistogram(mInputParams, callback); 237 238 assertThat(callbackLatch.await(CALLBACK_WAIT_MS, TimeUnit.MILLISECONDS)).isTrue(); 239 assertThat(callback.mIsSuccess).isTrue(); 240 241 verify(mAdServicesLoggerMock) 242 .logFledgeApiCallStats( 243 eq(LOGGING_API_NAME), 244 eq(TEST_PACKAGE_NAME), 245 eq(STATUS_USER_CONSENT_REVOKED), 246 anyInt()); 247 248 verifyNoMoreInteractions(mHistogramUpdaterMock); 249 } 250 251 @Test testWorkerInvalidArgumentFailureStopsAndNotifiesFailure()252 public void testWorkerInvalidArgumentFailureStopsAndNotifiesFailure() 253 throws InterruptedException { 254 doThrow(new IllegalArgumentException()) 255 .when(mHistogramUpdaterMock) 256 .updateNonWinHistogram(anyLong(), any(), anyInt(), any()); 257 258 CountDownLatch callbackLatch = new CountDownLatch(1); 259 UpdateAdCounterHistogramTestCallback callback = 260 new UpdateAdCounterHistogramTestCallback(callbackLatch); 261 262 mUpdateWorker.updateAdCounterHistogram(mInputParams, callback); 263 264 assertThat(callbackLatch.await(CALLBACK_WAIT_MS, TimeUnit.MILLISECONDS)).isTrue(); 265 assertThat(callback.mIsSuccess).isFalse(); 266 assertThat(callback.mFledgeErrorResponse.getStatusCode()) 267 .isEqualTo(STATUS_INVALID_ARGUMENT); 268 269 verify(mHistogramUpdaterMock) 270 .updateNonWinHistogram( 271 eq(AD_SELECTION_ID), 272 eq(TEST_PACKAGE_NAME), 273 eq(FrequencyCapFilters.AD_EVENT_TYPE_CLICK), 274 eq(CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI)); 275 276 verify(mAdServicesLoggerMock) 277 .logFledgeApiCallStats( 278 eq(LOGGING_API_NAME), 279 eq(TEST_PACKAGE_NAME), 280 eq(STATUS_INVALID_ARGUMENT), 281 anyInt()); 282 } 283 284 @Test testWorkerSuccessWithCallbackErrorLogsUnknownError()285 public void testWorkerSuccessWithCallbackErrorLogsUnknownError() throws InterruptedException { 286 CountDownLatch logCallbackErrorLatch = new CountDownLatch(1); 287 doAnswer( 288 unusedInput -> { 289 logCallbackErrorLatch.countDown(); 290 return null; 291 }) 292 .when(mAdServicesLoggerMock) 293 .logFledgeApiCallStats( 294 eq(LOGGING_API_NAME), 295 eq(TEST_PACKAGE_NAME), 296 eq(STATUS_UNKNOWN_ERROR), 297 anyInt()); 298 299 CountDownLatch callbackLatch = new CountDownLatch(1); 300 UpdateAdCounterHistogramTestCallback callback = 301 new UpdateAdCounterHistogramTestErrorCallback(callbackLatch); 302 303 mUpdateWorker.updateAdCounterHistogram(mInputParams, callback); 304 305 assertThat(callbackLatch.await(CALLBACK_WAIT_MS, TimeUnit.MILLISECONDS)).isTrue(); 306 assertThat(callback.mIsSuccess).isTrue(); 307 308 verify(mHistogramUpdaterMock) 309 .updateNonWinHistogram( 310 eq(AD_SELECTION_ID), 311 eq(TEST_PACKAGE_NAME), 312 eq(FrequencyCapFilters.AD_EVENT_TYPE_CLICK), 313 eq(CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI)); 314 315 verify(mAdServicesLoggerMock) 316 .logFledgeApiCallStats( 317 eq(LOGGING_API_NAME), eq(TEST_PACKAGE_NAME), eq(STATUS_SUCCESS), anyInt()); 318 319 assertThat(logCallbackErrorLatch.await(CALLBACK_WAIT_MS, TimeUnit.MILLISECONDS)).isTrue(); 320 verify(mAdServicesLoggerMock) 321 .logFledgeApiCallStats( 322 eq(LOGGING_API_NAME), 323 eq(TEST_PACKAGE_NAME), 324 eq(STATUS_UNKNOWN_ERROR), 325 anyInt()); 326 } 327 328 @Test testWorkerFailureWithCallbackErrorLogsUnknownError()329 public void testWorkerFailureWithCallbackErrorLogsUnknownError() throws InterruptedException { 330 doThrow(new IllegalStateException()) 331 .when(mHistogramUpdaterMock) 332 .updateNonWinHistogram(anyLong(), any(), anyInt(), any()); 333 334 CountDownLatch logCallbackErrorLatch = new CountDownLatch(1); 335 doAnswer( 336 unusedInput -> { 337 logCallbackErrorLatch.countDown(); 338 return null; 339 }) 340 .when(mAdServicesLoggerMock) 341 .logFledgeApiCallStats( 342 eq(LOGGING_API_NAME), 343 eq(TEST_PACKAGE_NAME), 344 eq(STATUS_UNKNOWN_ERROR), 345 anyInt()); 346 347 CountDownLatch callbackLatch = new CountDownLatch(1); 348 UpdateAdCounterHistogramTestCallback callback = 349 new UpdateAdCounterHistogramTestErrorCallback(callbackLatch); 350 351 mUpdateWorker.updateAdCounterHistogram(mInputParams, callback); 352 353 assertThat(callbackLatch.await(CALLBACK_WAIT_MS, TimeUnit.MILLISECONDS)).isTrue(); 354 assertThat(callback.mIsSuccess).isFalse(); 355 356 verify(mHistogramUpdaterMock) 357 .updateNonWinHistogram( 358 eq(AD_SELECTION_ID), 359 eq(TEST_PACKAGE_NAME), 360 eq(FrequencyCapFilters.AD_EVENT_TYPE_CLICK), 361 eq(CommonFixture.FIXED_NOW_TRUNCATED_TO_MILLI)); 362 363 verify(mAdServicesLoggerMock) 364 .logFledgeApiCallStats( 365 eq(LOGGING_API_NAME), 366 eq(TEST_PACKAGE_NAME), 367 eq(STATUS_INTERNAL_ERROR), 368 anyInt()); 369 370 assertThat(logCallbackErrorLatch.await(CALLBACK_WAIT_MS, TimeUnit.MILLISECONDS)).isTrue(); 371 verify(mAdServicesLoggerMock) 372 .logFledgeApiCallStats( 373 eq(LOGGING_API_NAME), 374 eq(TEST_PACKAGE_NAME), 375 eq(STATUS_UNKNOWN_ERROR), 376 anyInt()); 377 } 378 379 public static class UpdateAdCounterHistogramTestCallback 380 extends UpdateAdCounterHistogramCallback.Stub { 381 protected final CountDownLatch mCountDownLatch; 382 boolean mIsSuccess = false; 383 FledgeErrorResponse mFledgeErrorResponse; 384 UpdateAdCounterHistogramTestCallback(CountDownLatch countDownLatch)385 public UpdateAdCounterHistogramTestCallback(CountDownLatch countDownLatch) { 386 mCountDownLatch = countDownLatch; 387 } 388 389 @Override onSuccess()390 public void onSuccess() throws RemoteException { 391 mIsSuccess = true; 392 mCountDownLatch.countDown(); 393 } 394 395 @Override onFailure(FledgeErrorResponse fledgeErrorResponse)396 public void onFailure(FledgeErrorResponse fledgeErrorResponse) throws RemoteException { 397 mFledgeErrorResponse = fledgeErrorResponse; 398 mCountDownLatch.countDown(); 399 } 400 isSuccess()401 public boolean isSuccess() { 402 return mIsSuccess; 403 } 404 405 @Override toString()406 public String toString() { 407 return "UpdateAdCounterHistogramTestCallback{" 408 + "mCountDownLatch=" 409 + mCountDownLatch 410 + ", mIsSuccess=" 411 + mIsSuccess 412 + ", mFledgeErrorResponse=" 413 + mFledgeErrorResponse 414 + '}'; 415 } 416 } 417 418 public static class UpdateAdCounterHistogramTestErrorCallback 419 extends UpdateAdCounterHistogramTestCallback { UpdateAdCounterHistogramTestErrorCallback(CountDownLatch countDownLatch)420 public UpdateAdCounterHistogramTestErrorCallback(CountDownLatch countDownLatch) { 421 super(countDownLatch); 422 } 423 424 @Override onSuccess()425 public void onSuccess() throws RemoteException { 426 mIsSuccess = true; 427 mCountDownLatch.countDown(); 428 throw new RemoteException(); 429 } 430 431 @Override onFailure(FledgeErrorResponse fledgeErrorResponse)432 public void onFailure(FledgeErrorResponse fledgeErrorResponse) throws RemoteException { 433 mFledgeErrorResponse = fledgeErrorResponse; 434 mCountDownLatch.countDown(); 435 throw new RemoteException(); 436 } 437 } 438 439 public static class FlagsOverridingAdFiltering implements Flags { 440 private final boolean mShouldEnableAdFilteringFeature; 441 FlagsOverridingAdFiltering(boolean shouldEnableAdFilteringFeature)442 public FlagsOverridingAdFiltering(boolean shouldEnableAdFilteringFeature) { 443 mShouldEnableAdFilteringFeature = shouldEnableAdFilteringFeature; 444 } 445 FlagsOverridingAdFiltering()446 public FlagsOverridingAdFiltering() { 447 this(FLEDGE_FREQUENCY_CAP_FILTERING_ENABLED); 448 } 449 450 @Override getFledgeOnDeviceAuctionKillSwitch()451 public boolean getFledgeOnDeviceAuctionKillSwitch() { 452 return false; 453 } 454 455 @Override getEnforceIsolateMaxHeapSize()456 public boolean getEnforceIsolateMaxHeapSize() { 457 return false; 458 } 459 460 @Override getFledgeFrequencyCapFilteringEnabled()461 public boolean getFledgeFrequencyCapFilteringEnabled() { 462 return mShouldEnableAdFilteringFeature; 463 } 464 } 465 } 466