• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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